shopify-cli 1.11.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -1
  3. data/.github/CONTRIBUTING.md +7 -7
  4. data/.github/DESIGN.md +3 -3
  5. data/.github/PULL_REQUEST_TEMPLATE.md +1 -1
  6. data/.github/workflows/build.yml +1 -1
  7. data/.gitignore +3 -0
  8. data/.rubocop.yml +3 -1
  9. data/.ruby-version +1 -1
  10. data/CHANGELOG.md +48 -20
  11. data/Gemfile +4 -0
  12. data/Gemfile.lock +32 -0
  13. data/LICENSE +4 -1
  14. data/README.md +92 -26
  15. data/RELEASING.md +29 -7
  16. data/Rakefile +2 -2
  17. data/SECURITY.md +1 -1
  18. data/bin/load_shopify.rb +1 -1
  19. data/bin/shopify +3 -3
  20. data/dev.yml +1 -1
  21. data/docs/app/node/index.md +1 -1
  22. data/docs/app/rails/index.md +1 -1
  23. data/docs/core/index.md +1 -1
  24. data/docs/getting-started/index.md +1 -1
  25. data/docs/getting-started/install/index.md +1 -1
  26. data/docs/getting-started/migrate/index.md +1 -1
  27. data/docs/getting-started/uninstall/index.md +1 -1
  28. data/docs/getting-started/upgrade/index.md +1 -1
  29. data/docs/help/start-app/index.md +1 -1
  30. data/docs/index.md +1 -1
  31. data/ext/shopify-cli/extconf.rb +17 -5
  32. data/install.sh +1 -1
  33. data/lib/docgen/index_template.md.erb +2 -2
  34. data/lib/graphql/all_orgs_with_extensions.graphql +37 -0
  35. data/lib/graphql/find_organization.graphql +2 -1
  36. data/lib/project_types/extension/cli.rb +18 -15
  37. data/lib/project_types/extension/commands/build.rb +4 -5
  38. data/lib/project_types/extension/commands/connect.rb +35 -0
  39. data/lib/project_types/extension/commands/create.rb +12 -16
  40. data/lib/project_types/extension/commands/extension_command.rb +2 -2
  41. data/lib/project_types/extension/commands/info.rb +86 -0
  42. data/lib/project_types/extension/commands/push.rb +8 -7
  43. data/lib/project_types/extension/commands/register.rb +4 -5
  44. data/lib/project_types/extension/commands/serve.rb +5 -8
  45. data/lib/project_types/extension/commands/tunnel.rb +3 -1
  46. data/lib/project_types/extension/errors.rb +9 -0
  47. data/lib/project_types/extension/extension_project.rb +5 -0
  48. data/lib/project_types/extension/features/argo.rb +6 -6
  49. data/lib/project_types/extension/features/argo_runtime.rb +22 -38
  50. data/lib/project_types/extension/features/argo_serve.rb +25 -20
  51. data/lib/project_types/extension/forms/connect.rb +42 -0
  52. data/lib/project_types/extension/forms/questions/ask_name.rb +14 -6
  53. data/lib/project_types/extension/forms/questions/ask_registration.rb +51 -0
  54. data/lib/project_types/extension/messages/messages.rb +75 -11
  55. data/lib/project_types/extension/models/specification.rb +1 -0
  56. data/lib/project_types/extension/models/specification_handlers/{checkout_argo_extension.rb → checkout_ui_extension.rb} +3 -1
  57. data/lib/project_types/extension/models/specification_handlers/default.rb +21 -6
  58. data/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +86 -0
  59. data/lib/project_types/extension/models/specifications.rb +1 -0
  60. data/lib/project_types/extension/tasks/configure_features.rb +6 -7
  61. data/lib/project_types/extension/tasks/configure_options.rb +20 -0
  62. data/lib/project_types/extension/tasks/get_extensions.rb +32 -0
  63. data/lib/project_types/node/cli.rb +9 -21
  64. data/lib/project_types/node/commands/connect.rb +8 -2
  65. data/lib/project_types/node/commands/create.rb +9 -5
  66. data/lib/project_types/node/commands/deploy.rb +15 -5
  67. data/lib/project_types/node/commands/deploy/heroku.rb +29 -29
  68. data/lib/project_types/node/commands/generate.rb +4 -2
  69. data/lib/project_types/node/commands/open.rb +4 -2
  70. data/lib/project_types/node/commands/serve.rb +3 -2
  71. data/lib/project_types/node/commands/tunnel.rb +4 -2
  72. data/lib/project_types/node/messages/messages.rb +46 -89
  73. data/lib/project_types/rails/cli.rb +9 -21
  74. data/lib/project_types/rails/commands/connect.rb +8 -2
  75. data/lib/project_types/rails/commands/create.rb +10 -6
  76. data/lib/project_types/rails/commands/deploy.rb +15 -5
  77. data/lib/project_types/rails/commands/deploy/heroku.rb +84 -82
  78. data/lib/project_types/rails/commands/generate.rb +15 -5
  79. data/lib/project_types/rails/commands/generate/webhook.rb +28 -26
  80. data/lib/project_types/rails/commands/open.rb +4 -2
  81. data/lib/project_types/rails/commands/serve.rb +3 -2
  82. data/lib/project_types/rails/commands/tunnel.rb +4 -2
  83. data/lib/project_types/rails/messages/messages.rb +54 -101
  84. data/lib/project_types/script/cli.rb +18 -20
  85. data/lib/project_types/script/commands/create.rb +3 -1
  86. data/lib/project_types/script/commands/push.rb +12 -5
  87. data/lib/project_types/script/config/extension_points.yml +0 -3
  88. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +9 -3
  89. data/lib/project_types/script/layers/application/create_script.rb +6 -5
  90. data/lib/project_types/script/layers/application/push_script.rb +2 -1
  91. data/lib/project_types/script/layers/domain/errors.rb +6 -11
  92. data/lib/project_types/script/layers/domain/push_package.rb +4 -8
  93. data/lib/project_types/script/layers/domain/script_json.rb +32 -0
  94. data/lib/project_types/script/layers/domain/script_project.rb +1 -1
  95. data/lib/project_types/script/layers/infrastructure/errors.rb +14 -18
  96. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_project_creator.rb +105 -0
  97. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +103 -0
  98. data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +26 -0
  99. data/lib/project_types/script/layers/infrastructure/languages/rust_project_creator.rb +73 -0
  100. data/lib/project_types/script/layers/infrastructure/languages/rust_task_runner.rb +60 -0
  101. data/lib/project_types/script/layers/infrastructure/languages/task_runner.rb +21 -0
  102. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +2 -4
  103. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +45 -34
  104. data/lib/project_types/script/layers/infrastructure/script_service.rb +20 -14
  105. data/lib/project_types/script/messages/messages.rb +66 -55
  106. data/lib/project_types/script/tasks/ensure_env.rb +22 -1
  107. data/lib/project_types/script/ui/error_handler.rb +32 -32
  108. data/lib/project_types/theme/cli.rb +15 -27
  109. data/lib/project_types/theme/commands/check.rb +33 -0
  110. data/lib/project_types/theme/commands/delete.rb +64 -0
  111. data/lib/project_types/theme/commands/language_server.rb +16 -0
  112. data/lib/project_types/theme/commands/package.rb +55 -0
  113. data/lib/project_types/theme/commands/publish.rb +43 -0
  114. data/lib/project_types/theme/commands/pull.rb +51 -0
  115. data/lib/project_types/theme/commands/push.rb +58 -32
  116. data/lib/project_types/theme/commands/serve.rb +7 -17
  117. data/lib/project_types/theme/forms/confirm_store.rb +15 -0
  118. data/lib/project_types/theme/forms/select.rb +59 -0
  119. data/lib/project_types/theme/messages/messages.rb +110 -106
  120. data/lib/project_types/theme/ui/sync_progress_bar.rb +20 -0
  121. data/lib/shopify-cli/admin_api.rb +53 -35
  122. data/lib/shopify-cli/admin_api/populate_resource_command.rb +6 -14
  123. data/lib/shopify-cli/admin_api/schema.rb +1 -10
  124. data/lib/shopify-cli/api.rb +29 -14
  125. data/lib/shopify-cli/command.rb +15 -3
  126. data/lib/shopify-cli/commands.rb +7 -2
  127. data/lib/shopify-cli/commands/help.rb +2 -29
  128. data/lib/shopify-cli/commands/login.rb +95 -0
  129. data/lib/shopify-cli/commands/logout.rb +24 -8
  130. data/lib/shopify-cli/commands/populate.rb +23 -0
  131. data/lib/{project_types/node → shopify-cli}/commands/populate/customer.rb +2 -8
  132. data/lib/{project_types/node → shopify-cli}/commands/populate/draft_order.rb +2 -2
  133. data/lib/{project_types/node → shopify-cli}/commands/populate/product.rb +2 -8
  134. data/lib/shopify-cli/commands/store.rb +15 -0
  135. data/lib/shopify-cli/commands/switch.rb +39 -0
  136. data/lib/shopify-cli/commands/system.rb +12 -0
  137. data/lib/shopify-cli/commands/whoami.rb +28 -0
  138. data/lib/shopify-cli/connect.rb +32 -0
  139. data/lib/shopify-cli/context.rb +65 -4
  140. data/lib/shopify-cli/core/entry_point.rb +3 -22
  141. data/lib/shopify-cli/db.rb +4 -4
  142. data/lib/shopify-cli/http_request.rb +10 -0
  143. data/lib/shopify-cli/identity_auth.rb +282 -0
  144. data/lib/shopify-cli/{oauth → identity_auth}/servlet.rb +11 -12
  145. data/lib/shopify-cli/messages/messages.rb +133 -39
  146. data/lib/shopify-cli/partners_api.rb +21 -41
  147. data/lib/shopify-cli/partners_api/organizations.rb +8 -0
  148. data/lib/shopify-cli/project_commands.rb +16 -0
  149. data/lib/shopify-cli/project_type.rb +0 -31
  150. data/lib/shopify-cli/resources/env_file.rb +1 -1
  151. data/lib/shopify-cli/shopifolk.rb +8 -11
  152. data/lib/shopify-cli/sub_command.rb +1 -0
  153. data/lib/shopify-cli/tasks.rb +3 -0
  154. data/lib/shopify-cli/tasks/confirm_store.rb +18 -0
  155. data/lib/shopify-cli/tasks/create_api_client.rb +2 -2
  156. data/lib/shopify-cli/tasks/ensure_authenticated.rb +13 -0
  157. data/lib/shopify-cli/tasks/ensure_loopback_url.rb +1 -1
  158. data/lib/shopify-cli/tasks/ensure_project_type.rb +12 -0
  159. data/lib/shopify-cli/tasks/select_org_and_shop.rb +0 -3
  160. data/lib/shopify-cli/theme/dev_server.rb +98 -0
  161. data/lib/shopify-cli/theme/dev_server/certificate_manager.rb +79 -0
  162. data/lib/shopify-cli/theme/dev_server/header_hash.rb +94 -0
  163. data/lib/shopify-cli/theme/dev_server/hot-reload.js +93 -0
  164. data/lib/shopify-cli/theme/dev_server/hot_reload.rb +76 -0
  165. data/lib/shopify-cli/theme/dev_server/local_assets.rb +87 -0
  166. data/lib/shopify-cli/theme/dev_server/proxy.rb +205 -0
  167. data/lib/shopify-cli/theme/dev_server/sse.rb +75 -0
  168. data/lib/shopify-cli/theme/dev_server/watcher.rb +59 -0
  169. data/lib/shopify-cli/theme/dev_server/web_server.rb +140 -0
  170. data/lib/shopify-cli/theme/development_theme.rb +69 -0
  171. data/lib/shopify-cli/theme/file.rb +112 -0
  172. data/lib/shopify-cli/theme/ignore_filter.rb +109 -0
  173. data/lib/shopify-cli/theme/mime_type.rb +34 -0
  174. data/lib/shopify-cli/theme/syncer.rb +328 -0
  175. data/lib/shopify-cli/theme/theme.rb +204 -0
  176. data/lib/shopify-cli/version.rb +1 -1
  177. data/lib/shopify_cli.rb +18 -11
  178. data/shopify-cli.gemspec +12 -5
  179. data/shopify.fish +1 -1
  180. data/shopify.sh +1 -1
  181. metadata +95 -41
  182. data/.github/workflows/release.yml +0 -61
  183. data/lib/project_types/extension/features/argo_serve_options.rb +0 -40
  184. data/lib/project_types/node/commands/populate.rb +0 -23
  185. data/lib/project_types/rails/commands/populate.rb +0 -23
  186. data/lib/project_types/rails/commands/populate/customer.rb +0 -31
  187. data/lib/project_types/rails/commands/populate/draft_order.rb +0 -28
  188. data/lib/project_types/rails/commands/populate/product.rb +0 -30
  189. data/lib/project_types/script/layers/domain/config_ui.rb +0 -16
  190. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +0 -95
  191. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +0 -101
  192. data/lib/project_types/script/layers/infrastructure/project_creator.rb +0 -24
  193. data/lib/project_types/script/layers/infrastructure/rust_project_creator.rb +0 -71
  194. data/lib/project_types/script/layers/infrastructure/rust_task_runner.rb +0 -58
  195. data/lib/project_types/script/layers/infrastructure/task_runner.rb +0 -19
  196. data/lib/project_types/theme/commands/connect.rb +0 -54
  197. data/lib/project_types/theme/commands/create.rb +0 -48
  198. data/lib/project_types/theme/commands/deploy.rb +0 -38
  199. data/lib/project_types/theme/commands/generate.rb +0 -20
  200. data/lib/project_types/theme/commands/generate/env.rb +0 -79
  201. data/lib/project_types/theme/forms/connect.rb +0 -34
  202. data/lib/project_types/theme/forms/create.rb +0 -22
  203. data/lib/project_types/theme/tasks/ensure_themekit_installed.rb +0 -78
  204. data/lib/project_types/theme/themekit.rb +0 -113
  205. data/lib/shopify-cli/commands/connect.rb +0 -64
  206. data/lib/shopify-cli/commands/create.rb +0 -50
  207. data/lib/shopify-cli/oauth.rb +0 -198
@@ -45,9 +45,18 @@ module ShopifyCli
45
45
  # ShopifyCli::PartnersAPI.query(@ctx, 'all_organizations')
46
46
  #
47
47
  def query(ctx, query_name, **variables)
48
- authenticated_req(ctx) do
48
+ CLI::Kit::Util.begin do
49
49
  api_client(ctx).query(query_name, variables: variables)
50
+ end.retry_after(API::APIRequestUnauthorizedError, retries: 1) do
51
+ ShopifyCli::IdentityAuth.new(ctx: ctx).reauthenticate
50
52
  end
53
+ rescue API::APIRequestUnauthorizedError => e
54
+ if (request_info = auth_failure_info(ctx, e))
55
+ ctx.puts(ctx.message("core.api.error.failed_auth_debugging", request_info))
56
+ end
57
+ ctx.abort(ctx.message("core.api.error.failed_auth"))
58
+ rescue API::APIRequestNotFoundError
59
+ ctx.puts(ctx.message("core.partners_api.error.account_not_found", ShopifyCli::TOOL_NAME))
51
60
  end
52
61
 
53
62
  def partners_url_for(organization_id, api_client_id, local_debug)
@@ -59,15 +68,6 @@ module ShopifyCli
59
68
 
60
69
  private
61
70
 
62
- def authenticated_req(ctx)
63
- yield
64
- rescue API::APIRequestUnauthorizedError
65
- authenticate(ctx)
66
- retry
67
- rescue API::APIRequestNotFoundError
68
- ctx.puts(ctx.message("core.partners_api.error.account_not_found", ShopifyCli::TOOL_NAME))
69
- end
70
-
71
71
  def api_client(ctx)
72
72
  new(
73
73
  ctx: ctx,
@@ -77,37 +77,12 @@ module ShopifyCli
77
77
  end
78
78
 
79
79
  def access_token(ctx)
80
- ShopifyCli::DB.get(:identity_exchange_token) do
81
- authenticate(ctx)
82
- ShopifyCli::DB.get(:identity_exchange_token)
80
+ ShopifyCli::DB.get(:partners_exchange_token) do
81
+ IdentityAuth.new(ctx: ctx).authenticate
82
+ ShopifyCli::DB.get(:partners_exchange_token)
83
83
  end
84
84
  end
85
85
 
86
- def authenticate(ctx)
87
- OAuth.new(
88
- ctx: ctx,
89
- service: "identity",
90
- client_id: cli_id,
91
- scopes: scopes.join(" "),
92
- request_exchange: partners_id,
93
- ).authenticate("#{auth_endpoint}/oauth")
94
- end
95
-
96
- def partners_id
97
- return "271e16d403dfa18082ffb3d197bd2b5f4479c3fc32736d69296829cbb28d41a6" if ENV[LOCAL_DEBUG].nil?
98
- "df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978"
99
- end
100
-
101
- def cli_id
102
- return "fbdb2649-e327-4907-8f67-908d24cfd7e3" if ENV[LOCAL_DEBUG].nil?
103
- "e5380e02-312a-7408-5718-e07017e9cf52"
104
- end
105
-
106
- def auth_endpoint
107
- return "https://accounts.shopify.com" if ENV[LOCAL_DEBUG].nil?
108
- "https://identity.myshopify.io"
109
- end
110
-
111
86
  def endpoint
112
87
  return "https://partners.shopify.com" if ENV[LOCAL_DEBUG].nil?
113
88
  "https://partners.myshopify.io/"
@@ -122,10 +97,15 @@ module ShopifyCli
122
97
  "https://#{domain}"
123
98
  end
124
99
 
125
- def scopes
126
- %w[openid https://api.shopify.com/auth/partners.app.cli.access].tap do |result|
127
- result << "employee" if ShopifyCli::Shopifolk.acting_as_shopify_organization?
100
+ def auth_failure_info(ctx, error)
101
+ if error.response
102
+ headers = %w(www-authenticate x-request-id)
103
+ request_info = headers.map { |h| "#{h}: #{error.response[h]}" if error.response.key?(h) }.join("\n")
104
+ ctx.debug("Full headers: #{error.response.each_header.to_h}")
105
+ request_info
128
106
  end
107
+ rescue => e
108
+ ctx.debug("Couldn't fetch auth failure information from #{error}: #{e}")
129
109
  end
130
110
  end
131
111
 
@@ -26,6 +26,14 @@ module ShopifyCli
26
26
  org
27
27
  end
28
28
  end
29
+
30
+ def fetch_with_extensions(ctx, type)
31
+ resp = PartnersAPI.query(ctx, "all_orgs_with_extensions", type: type)
32
+ (resp&.dig("data", "organizations", "nodes") || []).map do |org|
33
+ org["apps"] = (org.dig("apps", "nodes") || [])
34
+ org
35
+ end
36
+ end
29
37
  end
30
38
  end
31
39
  end
@@ -0,0 +1,16 @@
1
+ module ShopifyCli
2
+ class ProjectCommands < Command
3
+ def call(*)
4
+ @ctx.puts(self.class.help)
5
+ end
6
+
7
+ def self.help
8
+ project_type = name.split("::")[0].downcase
9
+ ShopifyCli::Context.message(
10
+ "#{project_type}.help",
11
+ ShopifyCli::TOOL_NAME,
12
+ subcommand_registry.command_names.join(" | ")
13
+ )
14
+ end
15
+ end
16
+ end
@@ -5,7 +5,6 @@ module ShopifyCli
5
5
  class << self
6
6
  attr_accessor :project_type,
7
7
  :project_name,
8
- :project_creator_command_class,
9
8
  :project_load_shallow
10
9
 
11
10
  def repository
@@ -49,36 +48,6 @@ module ShopifyCli
49
48
  @project_name = name
50
49
  end
51
50
 
52
- def creator(command_const)
53
- @project_creator_command_class = command_const
54
- ShopifyCli::Commands::Create.subcommand(command_const, @project_type)
55
- end
56
-
57
- def create_command
58
- const_get(@project_creator_command_class)
59
- end
60
-
61
- def connector(command_const)
62
- @project_connector_command_class = command_const
63
- ShopifyCli::Commands::Connect.subcommand(command_const, @project_type)
64
- end
65
-
66
- def connect_command
67
- if @project_connector_command_class.nil?
68
- nil
69
- else
70
- const_get(@project_connector_command_class)
71
- end
72
- end
73
-
74
- def register_command(const, cmd)
75
- return if project_load_shallow
76
- Context.new.abort(
77
- Context.message("core.project_type.error.cannot_override_core", cmd, const)
78
- ) if Commands.core_command?(cmd)
79
- Commands.register(const, cmd)
80
- end
81
-
82
51
  def register_task(task, name)
83
52
  return if project_load_shallow
84
53
  ShopifyCli::Tasks.register(task, name)
@@ -22,7 +22,7 @@ module ShopifyCli
22
22
  def parse(directory)
23
23
  File.read(File.join(directory, FILENAME))
24
24
  .gsub("\r\n", "\n").split("\n").each_with_object({}) do |line, output|
25
- match = /\A([A-Za-z_0-9]+)=(.*)\z/.match(line)
25
+ match = /\A([A-Za-z_0-9]+)\s*=\s*(.*)\z/.match(line)
26
26
  if match
27
27
  key = match[1]
28
28
  output[key] = case match[2]
@@ -11,8 +11,6 @@ module ShopifyCli
11
11
  FEATURE_NAME = "shopifolk"
12
12
 
13
13
  class << self
14
- attr_writer :acting_as_shopify_organization
15
-
16
14
  ##
17
15
  # will return if the user appears to be a Shopify employee, based on several heuristics
18
16
  #
@@ -29,15 +27,16 @@ module ShopifyCli
29
27
  end
30
28
 
31
29
  def act_as_shopify_organization
32
- @acting_as_shopify_organization = true
30
+ DB.set(acting_as_shopify_organization: true)
33
31
  end
34
32
 
35
33
  def acting_as_shopify_organization?
36
- !!@acting_as_shopify_organization || (Project.has_current? && Project.current.config["shopify_organization"])
34
+ !!(DB.get(:acting_as_shopify_organization) ||
35
+ (Project.has_current? && Project.current.config["shopify_organization"]))
37
36
  end
38
37
 
39
38
  def reset
40
- @acting_as_shopify_organization = nil
39
+ DB.del(:acting_as_shopify_organization)
41
40
  end
42
41
  end
43
42
 
@@ -72,12 +71,10 @@ module ShopifyCli
72
71
  end
73
72
 
74
73
  def ini
75
- @ini ||= begin
76
- if File.exist?(GCLOUD_CONFIG_FILE)
77
- CLI::Kit::Ini
78
- .new(GCLOUD_CONFIG_FILE, default_section: "[#{SECTION}]", convert_types: false)
79
- .tap(&:parse).ini
80
- end
74
+ @ini ||= if File.exist?(GCLOUD_CONFIG_FILE)
75
+ CLI::Kit::Ini
76
+ .new(GCLOUD_CONFIG_FILE, default_section: "[#{SECTION}]", convert_types: false)
77
+ .tap(&:parse).ini
81
78
  end
82
79
  end
83
80
  end
@@ -9,6 +9,7 @@ module ShopifyCli
9
9
  args = cmd.options.parse(@_options, args[1..-1] || [])
10
10
  return call_help(parent_command, command_name) if cmd.options.help
11
11
  run_prerequisites
12
+
12
13
  cmd.call(args, command_name)
13
14
  end
14
15
  end
@@ -30,9 +30,12 @@ module ShopifyCli
30
30
  Registry.add(-> () { const_get(task) }, name)
31
31
  end
32
32
 
33
+ register :ConfirmStore, :confirm_store, "shopify-cli/tasks/confirm_store"
33
34
  register :CreateApiClient, :create_api_client, "shopify-cli/tasks/create_api_client"
35
+ register :EnsureAuthenticated, :ensure_authenticated, "shopify-cli/tasks/ensure_authenticated"
34
36
  register :EnsureEnv, :ensure_env, "shopify-cli/tasks/ensure_env"
35
37
  register :EnsureLoopbackURL, :ensure_loopback_url, "shopify-cli/tasks/ensure_loopback_url"
38
+ register :EnsureProjectType, :ensure_project_type, "shopify-cli/tasks/ensure_project_type"
36
39
  register :EnsureDevStore, :ensure_dev_store, "shopify-cli/tasks/ensure_dev_store"
37
40
  register :SelectOrgAndShop, :select_org_and_shop, "shopify-cli/tasks/select_org_and_shop"
38
41
  register :UpdateDashboardURLS, :update_dashboard_urls, "shopify-cli/tasks/update_dashboard_urls"
@@ -0,0 +1,18 @@
1
+ require "shopify_cli"
2
+
3
+ module ShopifyCli
4
+ module Tasks
5
+ class ConfirmStore < ShopifyCli::Task
6
+ def call(ctx)
7
+ @ctx = ctx
8
+ store = ShopifyCli::AdminAPI.get_shop_or_abort(ctx)
9
+ if CLI::UI::Prompt.confirm(ctx.message("core.tasks.confirm_store.prompt", store), default: false)
10
+ ctx.puts(ctx.message("core.tasks.confirm_store.confirmation", store))
11
+ else
12
+ ctx.puts(ctx.message("core.tasks.confirm_store.cancelling"))
13
+ raise AbortSilent
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -4,7 +4,7 @@ module ShopifyCli
4
4
  module Tasks
5
5
  class CreateApiClient < ShopifyCli::Task
6
6
  VALID_APP_TYPES = %w(public custom)
7
- DEFAULT_APP_URL = "https://shopify.github.io/shopify-app-cli/help/start-app/"
7
+ DEFAULT_APP_URL = "https://shopify.github.io/shopify-cli/help/start-app/"
8
8
 
9
9
  def call(ctx, org_id:, title:, type:)
10
10
  resp = ShopifyCli::PartnersAPI.query(
@@ -14,7 +14,7 @@ module ShopifyCli
14
14
  title: title,
15
15
  type: type,
16
16
  app_url: DEFAULT_APP_URL,
17
- redir: [OAuth::REDIRECT_HOST]
17
+ redir: [IdentityAuth::REDIRECT_HOST]
18
18
  )
19
19
 
20
20
  unless resp
@@ -0,0 +1,13 @@
1
+ require "shopify_cli"
2
+
3
+ module ShopifyCli
4
+ module Tasks
5
+ class EnsureAuthenticated < ShopifyCli::Task
6
+ def call(ctx)
7
+ ctx.abort(
8
+ ctx.message("core.identity_auth.login_prompt", ShopifyCli::TOOL_NAME)
9
+ ) unless ShopifyCli::IdentityAuth::IDENTITY_ACCESS_TOKENS.all? { |key| ShopifyCli::DB.exists?(key) }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -5,7 +5,7 @@ module ShopifyCli
5
5
  @ctx = ctx
6
6
  api_key = Project.current.env.api_key
7
7
  result = ShopifyCli::PartnersAPI.query(ctx, "get_app_urls", apiKey: api_key)
8
- loopback = OAuth::REDIRECT_HOST
8
+ loopback = IdentityAuth::REDIRECT_HOST
9
9
  app = result["data"]["app"]
10
10
  urls = app["redirectUrlWhitelist"]
11
11
  if urls.grep(/#{loopback}/).empty?
@@ -0,0 +1,12 @@
1
+ require "shopify_cli"
2
+
3
+ module ShopifyCli
4
+ module Tasks
5
+ class EnsureProjectType < ShopifyCli::Task
6
+ def call(ctx, project_type)
7
+ return true if project_type.to_sym == ShopifyCli::Project.current_project_type
8
+ ctx.abort(ctx.message("core.tasks.ensure_project_type.wrong_project_type", project_type))
9
+ end
10
+ end
11
+ end
12
+ end
@@ -8,9 +8,6 @@ module ShopifyCli
8
8
  def call(ctx, organization_id: nil, shop_domain: nil)
9
9
  @ctx = ctx
10
10
  return response(organization_id.to_i, shop_domain) unless organization_id.nil? || shop_domain.nil?
11
- if Shopifolk.check && wants_to_run_against_shopify_org?
12
- Shopifolk.act_as_shopify_organization
13
- end
14
11
  org = get_organization(organization_id)
15
12
  unless Shopifolk.acting_as_shopify_organization?
16
13
  shop_domain ||= get_shop_domain(org)
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+ require_relative "development_theme"
3
+ require_relative "ignore_filter"
4
+ require_relative "syncer"
5
+
6
+ require_relative "dev_server/hot_reload"
7
+ require_relative "dev_server/header_hash"
8
+ require_relative "dev_server/local_assets"
9
+ require_relative "dev_server/proxy"
10
+ require_relative "dev_server/sse"
11
+ require_relative "dev_server/watcher"
12
+ require_relative "dev_server/web_server"
13
+ require_relative "dev_server/certificate_manager"
14
+
15
+ require "pathname"
16
+
17
+ module ShopifyCli
18
+ module Theme
19
+ module DevServer
20
+ class << self
21
+ attr_accessor :ctx
22
+
23
+ def start(ctx, root, port: 9292, silent: false)
24
+ @ctx = ctx
25
+ theme = DevelopmentTheme.new(ctx, root: root)
26
+ ignore_filter = IgnoreFilter.from_path(root)
27
+ @syncer = Syncer.new(ctx, theme: theme, ignore_filter: ignore_filter)
28
+ watcher = Watcher.new(ctx, theme: theme, syncer: @syncer, ignore_filter: ignore_filter)
29
+
30
+ # Setup the middleware stack. Mimics Rack::Builder / config.ru, but in reverse order
31
+ @app = Proxy.new(ctx, theme: theme, syncer: @syncer)
32
+ @app = LocalAssets.new(ctx, @app, theme: theme)
33
+ @app = HotReload.new(ctx, @app, theme: theme, watcher: watcher, ignore_filter: ignore_filter)
34
+ stopped = false
35
+
36
+ theme.ensure_exists!
37
+
38
+ trap("INT") do
39
+ stopped = true
40
+ stop
41
+ end
42
+
43
+ CLI::UI::Frame.open(@ctx.message("theme.serve.serve")) do
44
+ ctx.print_task("Syncing theme ##{theme.id} on #{theme.shop}")
45
+ @syncer.start_threads
46
+ if silent
47
+ @syncer.upload_theme!(delay_low_priority_files: true)
48
+ else
49
+ @syncer.upload_theme_with_progress_bar!(delay_low_priority_files: true)
50
+ end
51
+
52
+ return if stopped
53
+
54
+ ctx.puts("")
55
+ ctx.puts("Serving #{theme.root}")
56
+ ctx.puts("")
57
+ ctx.open_url!("http://127.0.0.1:#{port}")
58
+ ctx.puts("")
59
+ ctx.puts("Customize this theme in the Online Store Editor:")
60
+ ctx.puts("{{green:#{theme.editor_url}}}")
61
+ ctx.puts("")
62
+ ctx.puts("Share this theme preview:")
63
+ ctx.puts("{{green:#{theme.preview_url}}}")
64
+ ctx.puts("")
65
+ ctx.puts("(Use Ctrl-C to stop)")
66
+ end
67
+
68
+ logger = if ctx.debug?
69
+ WEBrick::Log.new(nil, WEBrick::BasicLog::INFO)
70
+ else
71
+ WEBrick::Log.new(nil, WEBrick::BasicLog::FATAL)
72
+ end
73
+
74
+ watcher.start
75
+ WebServer.run(
76
+ @app,
77
+ Port: port,
78
+ Logger: logger,
79
+ AccessLog: [],
80
+ )
81
+ watcher.stop
82
+
83
+ rescue ShopifyCli::API::APIRequestForbiddenError,
84
+ ShopifyCli::API::APIRequestUnauthorizedError
85
+ @ctx.abort("You are not authorized to edit themes on #{theme.shop}.\n" \
86
+ "Make sure you are a user of that store, and allowed to edit themes.")
87
+ end
88
+
89
+ def stop
90
+ @ctx.puts("Stopping ...")
91
+ @app.close
92
+ @syncer.shutdown
93
+ WebServer.shutdown
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module ShopifyCli
6
+ module Theme
7
+ module DevServer
8
+ class CertificateManager
9
+ attr_reader :ctx, :domain_name, :certificate, :private_key
10
+
11
+ ISSUER_EXTENSIONS = [
12
+ ["subjectKeyIdentifier", "hash", false],
13
+ ["authorityKeyIdentifier", "keyid:always", false],
14
+ ]
15
+
16
+ def initialize(ctx, domain_name)
17
+ @ctx = ctx
18
+ @domain_name = domain_name
19
+ end
20
+
21
+ def find_or_create_certificates!
22
+ @private_key = if (private_key_pem = ShopifyCli::DB.get(:ssl_private_key))
23
+ OpenSSL::PKey::RSA.new(private_key_pem)
24
+ else
25
+ OpenSSL::PKey::RSA.new(2048)
26
+ end
27
+
28
+ @certificate = if (certificate_pem = ShopifyCli::DB.get(:ssl_certificate))
29
+ OpenSSL::X509::Certificate.new(certificate_pem)
30
+ else
31
+ x509_certificate = build_x509_certificate
32
+
33
+ sign_certificate!(x509_certificate)
34
+
35
+ x509_certificate
36
+ end
37
+
38
+ ShopifyCli::DB.set(ssl_certificate: certificate.to_pem)
39
+ ShopifyCli::DB.set(ssl_private_key: private_key.to_pem)
40
+ end
41
+
42
+ private
43
+
44
+ def build_x509_certificate
45
+ certificate = OpenSSL::X509::Certificate.new
46
+
47
+ certificate.public_key = private_key.public_key
48
+ certificate.subject = subject
49
+ certificate.version = 2
50
+ certificate.serial = 0x0
51
+
52
+ certificate.not_before = Time.now.utc
53
+ certificate.not_after = Time.now.utc + 365 * 24 * 60 * 60
54
+
55
+ certificate
56
+ end
57
+
58
+ def sign_certificate!(certificate)
59
+ ef = OpenSSL::X509::ExtensionFactory.new
60
+
61
+ ef.subject_certificate = certificate
62
+ ef.issuer_certificate = certificate
63
+
64
+ ISSUER_EXTENSIONS.each do |args|
65
+ certificate.add_extension(ef.create_extension(*args))
66
+ end
67
+
68
+ certificate.add_extension(ef.create_extension("subjectAltName", "DNS:#{@domain_name}", false))
69
+
70
+ certificate.sign(private_key, OpenSSL::Digest.new("SHA256"))
71
+ end
72
+
73
+ def subject
74
+ OpenSSL::X509::Name.parse("/CN=#{@domain_name}/")
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end