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
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Extension
4
+ module Forms
5
+ class Connect < ShopifyCli::Form
6
+ attr_reader :registration, :app
7
+
8
+ flag_arguments :type
9
+
10
+ class ExtensionProjectDetails
11
+ include SmartProperties
12
+
13
+ property :registration, accepts: Models::Registration
14
+ property :app, accepts: Models::App
15
+
16
+ def complete?
17
+ !!(registration && app)
18
+ end
19
+ end
20
+
21
+ def ask
22
+ ShopifyCli::Result.wrap(ExtensionProjectDetails.new)
23
+ .then(&Questions::AskRegistration.new(ctx: ctx, type: type))
24
+ .unwrap { |e| raise e }
25
+ .tap do |project_details|
26
+ ctx.abort(ctx.message("connect.incomplete_configuration")) unless project_details.complete?
27
+
28
+ self.registration = project_details.registration
29
+ self.app = project_details.app
30
+ end
31
+ end
32
+
33
+ def directory_name
34
+ name.strip.gsub(/( )/, "_").downcase
35
+ end
36
+
37
+ private
38
+
39
+ attr_writer :registration, :app
40
+ end
41
+ end
42
+ end
@@ -11,17 +11,25 @@ module Extension
11
11
  default: -> { CLI::UI::Prompt.method(:ask) }
12
12
 
13
13
  def call(project_details)
14
- project_details.name = ask_with_reprompt(
15
- initial_value: name,
16
- break_condition: -> (current_name) { Models::Registration.valid_title?(current_name) },
17
- prompt_message: ctx.message("create.ask_name"),
18
- reprompt_message: ctx.message("create.invalid_name", Models::Registration::MAX_TITLE_LENGTH)
19
- )
14
+ if theme_app_extension?(project_details)
15
+ project_details.name = name || "theme-app-extension"
16
+ else
17
+ project_details.name = ask_with_reprompt(
18
+ initial_value: name,
19
+ break_condition: -> (current_name) { Models::Registration.valid_title?(current_name) },
20
+ prompt_message: ctx.message("create.ask_name"),
21
+ reprompt_message: ctx.message("create.invalid_name", Models::Registration::MAX_TITLE_LENGTH)
22
+ )
23
+ end
20
24
  project_details
21
25
  end
22
26
 
23
27
  private
24
28
 
29
+ def theme_app_extension?(project_details)
30
+ project_details&.type&.identifier == "THEME_APP_EXTENSION"
31
+ end
32
+
25
33
  def ask_with_reprompt(initial_value:, break_condition:, prompt_message:, reprompt_message:)
26
34
  value = initial_value
27
35
  reprompt = false
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Extension
4
+ module Forms
5
+ module Questions
6
+ class AskRegistration
7
+ include ShopifyCli::MethodObject
8
+
9
+ property! :ctx
10
+ property! :type
11
+ property! :prompt,
12
+ converts: :to_proc,
13
+ default: -> { CLI::UI::Prompt.method(:ask) }
14
+
15
+ def call(project_details)
16
+ project_details.tap(&method(:prompt_for_registration))
17
+ end
18
+
19
+ private
20
+
21
+ def prompt_for_registration(project_details)
22
+ apps_and_registrations = load_registrations(type)
23
+ app, registration = choose_interactively(apps_and_registrations)
24
+ project_details.app = app
25
+ project_details.registration = registration
26
+ end
27
+
28
+ def choose_interactively(apps_and_registrations)
29
+ prompt.call(ctx.message("connect.ask_registration")) do |handler|
30
+ apps_and_registrations.each do |(app, extension)|
31
+ handler.option("#{app.title} by #{app.business_name}: #{extension.title}") { [app, extension] }
32
+ end
33
+ end
34
+ end
35
+
36
+ def load_registrations(type)
37
+ ctx.puts(@ctx.message("connect.loading_extensions"))
38
+ registrations = Tasks::GetExtensions.call(context: ctx, type: type)
39
+
40
+ registrations.empty? ? abort_no_registrations : registrations
41
+ end
42
+
43
+ def abort_no_registrations
44
+ ctx.puts(@ctx.message("connect.no_extensions", type))
45
+ ctx.puts(@ctx.message("connect.learn_about_extensions"))
46
+ raise ShopifyCli::AbortSilent
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -4,7 +4,21 @@ require "shopify_cli"
4
4
  module Extension
5
5
  module Messages
6
6
  MESSAGES = {
7
+ extension: {
8
+ help: <<~HELP,
9
+ Suite of commands for developing app extensions. See {{command:%1$s extension <command> --help}} for usage of each command.
10
+ Usage: {{command:%1$s extension [ %2$s ]}}
11
+ HELP
12
+ },
7
13
  create: {
14
+ help: <<~HELP,
15
+ Create a new app extension.
16
+ Usage: {{command:%s extension create <name>}}
17
+ Options:
18
+ {{command:--type=TYPE}} The type of extension you would like to create.
19
+ {{command:--name=NAME}} The name of your extension (50 characters).
20
+ {{command:--api-key=KEY}} The API key of your app.
21
+ HELP
8
22
  ask_name: "Extension name",
9
23
  invalid_name: "Extension name must be under %s characters",
10
24
  ask_type: "What type of extension are you creating?",
@@ -13,13 +27,13 @@ module Extension
13
27
  ready_to_start: <<~MESSAGE,
14
28
  {{v}} A new folder was generated at {{green:./%s}}.
15
29
  {{*}} You’re ready to start building {{green:%s}}!
16
- Navigate to the new folder, then run {{command:shopify serve}} to start a local server.
30
+ Navigate to the new folder, then run {{command:shopify extension serve}} to start a local server.
17
31
  MESSAGE
18
32
  learn_more: <<~MESSAGE,
19
33
  {{*}} Once you're ready to version and publish your extension,
20
- run {{command:shopify register}} to register this extension with one of your apps.
34
+ run {{command:shopify extension register}} to register this extension with one of your apps.
21
35
  MESSAGE
22
- try_again: "{{*}} Fix the errors and run {{command:shopify create extension}} again.",
36
+ try_again: "{{*}} Fix the errors and run {{command:shopify extension create}} again.",
23
37
  errors: {
24
38
  directory_exists: "Directory ‘%s’ already exists. Please remove it or choose a new name for your project.",
25
39
  },
@@ -28,15 +42,39 @@ module Extension
28
42
  ask_app: "Which app would you like to register this extension with?",
29
43
  no_apps: "{{x}} You don’t have any apps.",
30
44
  learn_about_apps: "{{*}} Learn more about building apps at <https://shopify.dev/concepts/apps>, " \
31
- "or try creating a new app using {{command:shopify create}}.",
45
+ "or try creating a new app using {{command:shopify [node|rails] create}}.",
32
46
  loading_apps: "Loading your apps...",
33
47
  no_available_extensions: "{{x}} There are no available extensions for this app.",
34
48
  },
49
+ connect: {
50
+ connected: "Project now connected to {{green:%s: %s}}",
51
+ incomplete_configuration: "Cannot connect extension due to missing configuration information",
52
+ invalid_api_key: "The API key %s does not match any of your apps.",
53
+ ask_registration: "Which extension would you like to connect to?",
54
+ loading_extensions: "Loading your extensions...",
55
+ no_extensions: "{{x}} You don't have any extensions of type %s",
56
+ learn_about_extensions: "{{*}} Learn more about building extensions at <https://shopify.dev/concepts/apps>, " \
57
+ "or try creating a new extension using {{command:shopify extension create}}.",
58
+ help: <<~HELP,
59
+ {{command:%s extension connect}}: Connects an existing extension to Shopify CLI. Creates a config file.
60
+ Usage: {{command:%s extension connect}}
61
+ HELP
62
+ },
35
63
  build: {
64
+ help: <<~HELP,
65
+ Build your extension to prepare for deployment.
66
+ Usage: {{command:%s extension build}}
67
+ HELP
36
68
  frame_title: "Building extension with: %s...",
37
69
  build_failure_message: "Failed to build extension code.",
38
70
  },
39
71
  register: {
72
+ help: <<~HELP,
73
+ Register your local extension to a Shopify app
74
+ Usage: {{command:%s extension register}}
75
+ Options:
76
+ {{command:--api-key=API_KEY}} The API key used to register an app with the extension. This can be found on the app page on Partners Dashboard.
77
+ HELP
40
78
  frame_title: "Registering Extension",
41
79
  waiting_text: "Registering with Shopify...",
42
80
  already_registered: "Extension is already registered.",
@@ -44,17 +82,28 @@ module Extension
44
82
  confirm_question: "Would you like to register this extension? (y/n)",
45
83
  confirm_abort: "Extension was not registered.",
46
84
  success: "{{v}} Registered {{green:%s}}.",
47
- success_info: "{{*}} Run {{command:shopify push}} to push your extension to Shopify.",
85
+ success_info: "{{*}} Run {{command:shopify extension push}} to push your extension to Shopify.",
48
86
  },
49
87
  push: {
88
+ help: <<~HELP,
89
+ Push the current extension to Shopify.
90
+ Usage: {{command:%s extension push}}
91
+ HELP
50
92
  frame_title: "Pushing your extension to Shopify",
51
93
  waiting_text: "Pushing code to Shopify...",
52
94
  pushed_with_errors: "{{x}} Code pushed to Shopify with errors on %s.",
53
- push_with_errors_info: "{{*}} Fix these errors and run {{command:shopify push}} to revalidate your extension.",
95
+ push_with_errors_info: "{{*}} Fix these errors and run {{command:shopify extension push}} to " \
96
+ "revalidate your extension.",
54
97
  success_confirmation: "{{v}} Pushed {{green:%s}} to a draft on %s.",
55
98
  success_info: "{{*}} Visit %s to version and publish your extension.",
56
99
  },
57
100
  serve: {
101
+ help: <<~HELP,
102
+ Serve your extension in a local simulator for development.
103
+ Usage: {{command:%s extension serve}}
104
+ Options:
105
+ {{command:--tunnel=TUNNEL}} Establish an ngrok tunnel (default: false)
106
+ HELP
58
107
  frame_title: "Serving extension...",
59
108
  no_available_ports_found: "No available ports found to run extension.",
60
109
  serve_failure_message: "Failed to run extension code.",
@@ -69,25 +118,25 @@ module Extension
69
118
  tunnel_running_at: "Tunnel running at: {{underline:%s}}",
70
119
  help: <<~HELP,
71
120
  Start or stop an http tunnel to your local development extension using ngrok.
72
- Usage: {{command:%s tunnel [ auth | start | stop | status ]}}
121
+ Usage: {{command:%s extension tunnel [ auth | start | stop | status ]}}
73
122
  HELP
74
123
  extended_help: <<~HELP,
75
124
  {{bold:Subcommands:}}
76
125
 
77
126
  {{cyan:auth}}: Writes an ngrok auth token to ~/.ngrok2/ngrok.yml to connect with an ngrok account.
78
127
  Visit https://dashboard.ngrok.com/signup to sign up.
79
- Usage: {{command:%1$s tunnel auth <token>}}
128
+ Usage: {{command:%1$s extension tunnel auth <token>}}
80
129
 
81
130
  {{cyan:start}}: Starts an ngrok tunnel, will print the URL for an existing tunnel if already running.
82
- Usage: {{command:%1$s tunnel start}}
131
+ Usage: {{command:%1$s extension tunnel start}}
83
132
  Options:
84
133
  {{command:--port=PORT}} Forward the ngrok subdomain to local port PORT. Defaults to %2$s.
85
134
 
86
135
  {{cyan:stop}}: Stops the ngrok tunnel.
87
- Usage: {{command:%1$s tunnel stop}}
136
+ Usage: {{command:%1$s extension tunnel stop}}
88
137
 
89
138
  {{cyan:status}}: Output the current status of the ngrok tunnel.
90
- Usage: {{command:%1$s tunnel status}}
139
+ Usage: {{command:%1$s extension tunnel status}}
91
140
  HELP
92
141
  },
93
142
  features: {
@@ -134,6 +183,21 @@ module Extension
134
183
  checkout_post_purchase: {
135
184
  name: "Checkout Post Purchase",
136
185
  },
186
+ theme_app_extension: {
187
+ name: "Theme App Extension",
188
+ tagline: "(limit 1 per app)",
189
+ overrides: {
190
+ register: {
191
+ confirm_info: "You can only create one %s extension per app, which can’t be undone.",
192
+ },
193
+ create: {
194
+ ready_to_start: <<~MESSAGE,
195
+ {{v}} A new folder was generated at {{green:./%s}}.
196
+ {{*}} You’re ready to start building {{green:%s}}!
197
+ MESSAGE
198
+ },
199
+ },
200
+ },
137
201
  }
138
202
  end
139
203
  end
@@ -17,6 +17,7 @@ module Extension
17
17
 
18
18
  def self.build(feature_set_attributes)
19
19
  feature_set_attributes.each_with_object(OpenStruct.new) do |(identifier, feature_attributes), feature_set|
20
+ next if feature_attributes.nil?
20
21
  feature_set[identifier] = ShopifyCli::ResolveConstant
21
22
  .call(identifier, namespace: Features)
22
23
  .rescue { OpenStruct }
@@ -3,7 +3,7 @@
3
3
  module Extension
4
4
  module Models
5
5
  module SpecificationHandlers
6
- class CheckoutArgoExtension < Default
6
+ class CheckoutUiExtension < Default
7
7
  PERMITTED_CONFIG_KEYS = [:metafields, :extension_points]
8
8
 
9
9
  def config(context)
@@ -13,6 +13,8 @@ module Extension
13
13
  }
14
14
  end
15
15
  end
16
+
17
+ CheckoutArgoExtension = CheckoutUiExtension
16
18
  end
17
19
  end
18
20
  end
@@ -30,7 +30,7 @@ module Extension
30
30
  argo.config(context)
31
31
  end
32
32
 
33
- def create(directory_name, context)
33
+ def create(directory_name, context, **_args)
34
34
  argo.create(directory_name, identifier, context)
35
35
  end
36
36
 
@@ -43,16 +43,21 @@ module Extension
43
43
  end
44
44
 
45
45
  def choose_port?(context)
46
- argo_runtime(context).accepts_port?
46
+ argo_runtime(context).supports?(:port)
47
47
  end
48
48
 
49
49
  def establish_tunnel?(context)
50
- argo_runtime(context).accepts_tunnel_url?
50
+ argo_runtime(context).supports?(:public_url)
51
51
  end
52
52
 
53
53
  def serve(context:, port:, tunnel_url:)
54
- Features::ArgoServe.new(specification_handler: self, argo_runtime: argo_runtime(context),
55
- context: context, port: port, tunnel_url: tunnel_url).call
54
+ Features::ArgoServe.new(
55
+ specification_handler: self,
56
+ argo_runtime: argo_runtime(context),
57
+ context: context,
58
+ port: port,
59
+ tunnel_url: tunnel_url,
60
+ ).call
56
61
  end
57
62
 
58
63
  def renderer_package(context)
@@ -62,7 +67,7 @@ module Extension
62
67
  def argo_runtime(context)
63
68
  @argo_runtime ||= Features::ArgoRuntime.new(
64
69
  renderer: renderer_package(context),
65
- cli: cli_package(context)
70
+ cli: cli_package(context),
66
71
  )
67
72
  end
68
73
 
@@ -75,6 +80,16 @@ module Extension
75
80
  .unwrap { |_e| context.abort(context.message("errors.package_not_found", cli_package_name)) }
76
81
  end
77
82
 
83
+ def message_for_extension(key, *params)
84
+ override_key = "overrides.#{key}"
85
+ key_parts = override_key.split(".").map(&:to_sym)
86
+ if (str = messages.dig(*key_parts))
87
+ str % params
88
+ else
89
+ ShopifyCli::Context.message(key, *params)
90
+ end
91
+ end
92
+
78
93
  protected
79
94
 
80
95
  def argo
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+ require "base64"
3
+ require "json"
4
+
5
+ module Extension
6
+ module Models
7
+ module SpecificationHandlers
8
+ class ThemeAppExtension < Default
9
+ SUPPORTED_BUCKETS = %w(assets blocks snippets)
10
+ BUNDLE_SIZE_LIMIT = 10 * 1024 * 1024 # 10MB
11
+ LIQUID_SIZE_LIMIT = 100 * 1024 # 100kb
12
+ SUPPORTED_ASSET_EXTS = %w(.jpg .js .css .png .svg)
13
+
14
+ def create(directory_name, context, getting_started: false)
15
+ context.root = File.join(context.root, directory_name)
16
+
17
+ if getting_started
18
+ ShopifyCli::Git.clone("https://github.com/Shopify/theme-extension-getting-started", context.root)
19
+ context.rm_r(".git")
20
+ else
21
+ FileUtils.makedirs(SUPPORTED_BUCKETS.map { |b| File.join(context.root, b) })
22
+ end
23
+ end
24
+
25
+ def config(context)
26
+ current_size = 0
27
+ current_liquid_size = 0
28
+ Dir.chdir(context.root) do
29
+ Dir["**/*"].select { |filename| File.file?(filename) && validate(filename) }
30
+ .map do |filename|
31
+ dirname = File.dirname(filename)
32
+ if dirname == "assets"
33
+ # Assets should be read as binary data, since they could be images
34
+ mode = "rb"
35
+ encoding = "BINARY"
36
+ else
37
+ # Other assets should be treated as UTF-8 encoded text
38
+ mode = "rt"
39
+ encoding = "UTF-8"
40
+ current_liquid_size += File.size(filename)
41
+ end
42
+ current_size += File.size(filename)
43
+ if current_size > BUNDLE_SIZE_LIMIT
44
+ raise Extension::Errors::BundleTooLargeError,
45
+ "Total size of all files must be less than #{CLI::Kit::Util.to_filesize(BUNDLE_SIZE_LIMIT)}"
46
+ end
47
+ if current_liquid_size > LIQUID_SIZE_LIMIT
48
+ raise Extension::Errors::BundleTooLargeError,
49
+ "Total size of all liquid must be less than #{CLI::Kit::Util.to_filesize(LIQUID_SIZE_LIMIT)}"
50
+ end
51
+ [filename, Base64.encode64(File.read(filename, mode: mode, encoding: encoding))]
52
+ end
53
+ .yield_self do |encoded_files_by_name|
54
+ { "theme_extension" => { "files" => encoded_files_by_name.to_h } }
55
+ end
56
+ end
57
+ end
58
+
59
+ def name
60
+ "Theme App Extension"
61
+ end
62
+
63
+ private
64
+
65
+ def validate(filename)
66
+ dirname = File.dirname(filename)
67
+ unless SUPPORTED_BUCKETS.include?(dirname)
68
+ raise Extension::Errors::InvalidFilenameError, "Invalid directory: #{dirname}"
69
+ end
70
+
71
+ ext = File.extname(filename)
72
+ if dirname == "assets"
73
+ unless SUPPORTED_ASSET_EXTS.include?(ext)
74
+ raise Extension::Errors::InvalidFilenameError,
75
+ "Invalid filename: #{filename}; #{ext} is not supported"
76
+ end
77
+ elsif ext != ".liquid"
78
+ raise Extension::Errors::InvalidFilenameError,
79
+ "Invalid filename: #{filename}; Only .liquid allowed in #{dirname}"
80
+ end
81
+ true
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end