@shopify/cli-kit 3.43.0 → 3.44.1-pre.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 (140) hide show
  1. package/README.md +15 -7
  2. package/assets/cli-ruby/Gemfile +6 -3
  3. package/assets/cli-ruby/lib/project_types/extension/commands/serve.rb +10 -0
  4. package/assets/cli-ruby/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +1 -1
  5. package/assets/cli-ruby/lib/project_types/theme/cli.rb +1 -0
  6. package/assets/cli-ruby/lib/project_types/theme/commands/common/root_helper.rb +3 -2
  7. package/assets/cli-ruby/lib/project_types/theme/commands/console.rb +15 -0
  8. package/assets/cli-ruby/lib/project_types/theme/commands/push.rb +1 -0
  9. package/assets/cli-ruby/lib/project_types/theme/messages/messages.rb +4 -2
  10. package/assets/cli-ruby/lib/shopify_cli/constants.rb +1 -0
  11. package/assets/cli-ruby/lib/shopify_cli/environment.rb +4 -0
  12. package/assets/cli-ruby/lib/shopify_cli/theme/dev_server/hot_reload/script_injector.rb +1 -1
  13. package/assets/cli-ruby/lib/shopify_cli/theme/dev_server/hot_reload.rb +9 -2
  14. package/assets/cli-ruby/lib/shopify_cli/theme/dev_server/proxy.rb +13 -12
  15. package/assets/cli-ruby/lib/shopify_cli/theme/dev_server.rb +2 -2
  16. package/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb +7 -4
  17. package/assets/cli-ruby/lib/shopify_cli/theme/extension/host_theme.rb +19 -14
  18. package/assets/cli-ruby/lib/shopify_cli/theme/repl/api.rb +107 -0
  19. package/assets/cli-ruby/lib/shopify_cli/theme/repl/auth_dev_server.rb +80 -0
  20. package/assets/cli-ruby/lib/shopify_cli/theme/repl/auth_middleware.rb +73 -0
  21. package/assets/cli-ruby/lib/shopify_cli/theme/repl/resources/success.html +79 -0
  22. package/assets/cli-ruby/lib/shopify_cli/theme/repl/resources/template.liquid +15 -0
  23. package/assets/cli-ruby/lib/shopify_cli/theme/repl.rb +145 -0
  24. package/assets/cli-ruby/lib/shopify_cli/theme/syncer/standard_reporter.rb +2 -2
  25. package/assets/cli-ruby/lib/shopify_cli/theme/syncer.rb +1 -1
  26. package/assets/cli-ruby/lib/shopify_cli/theme/theme.rb +6 -6
  27. package/dist/private/node/analytics.d.ts +2 -2
  28. package/dist/private/node/analytics.js.map +1 -1
  29. package/dist/private/node/api/graphql.js +9 -16
  30. package/dist/private/node/api/graphql.js.map +1 -1
  31. package/dist/private/node/api/headers.js +8 -5
  32. package/dist/private/node/api/headers.js.map +1 -1
  33. package/dist/private/node/api.d.ts +9 -0
  34. package/dist/private/node/api.js +19 -0
  35. package/dist/private/node/api.js.map +1 -1
  36. package/dist/private/node/colors.js.map +1 -0
  37. package/dist/private/node/conf-store.d.ts +24 -5
  38. package/dist/private/node/conf-store.js +21 -3
  39. package/dist/private/node/conf-store.js.map +1 -1
  40. package/dist/private/node/constants.d.ts +2 -0
  41. package/dist/private/node/constants.js +2 -0
  42. package/dist/private/node/constants.js.map +1 -1
  43. package/dist/private/node/content-tokens.js +1 -1
  44. package/dist/private/node/content-tokens.js.map +1 -1
  45. package/dist/private/node/semver.js.map +1 -0
  46. package/dist/private/node/session/identity-token-validation.d.ts +1 -1
  47. package/dist/private/node/session/identity-token-validation.js +45 -20
  48. package/dist/private/node/session/identity-token-validation.js.map +1 -1
  49. package/dist/private/node/session/store.js +3 -46
  50. package/dist/private/node/session/store.js.map +1 -1
  51. package/dist/private/node/session.js +13 -10
  52. package/dist/private/node/session.js.map +1 -1
  53. package/dist/private/node/themes/generate-theme-name.js.map +1 -0
  54. package/dist/private/node/themes/replace-invalid-characters.js.map +1 -0
  55. package/dist/private/node/themes/themes-api/headers.js.map +1 -0
  56. package/dist/private/node/themes/themes-api/retry.js.map +1 -0
  57. package/dist/private/node/themes/themes-api/throttler.js.map +1 -0
  58. package/dist/private/node/ui/components/ConcurrentOutput.d.ts +0 -1
  59. package/dist/private/node/ui/components/ConcurrentOutput.js +0 -1
  60. package/dist/private/node/ui/components/ConcurrentOutput.js.map +1 -1
  61. package/dist/public/common/string.d.ts +16 -4
  62. package/dist/public/common/string.js +25 -4
  63. package/dist/public/common/string.js.map +1 -1
  64. package/dist/public/common/ts/deep-required.d.ts +0 -1
  65. package/dist/public/common/ts/deep-required.js.map +1 -1
  66. package/dist/public/common/ts/pick-by-prefix.d.ts +0 -1
  67. package/dist/public/common/ts/pick-by-prefix.js.map +1 -1
  68. package/dist/public/common/version.d.ts +1 -1
  69. package/dist/public/common/version.js +1 -1
  70. package/dist/public/common/version.js.map +1 -1
  71. package/dist/public/node/base-command.d.ts +8 -5
  72. package/dist/public/node/base-command.js +1 -1
  73. package/dist/public/node/base-command.js.map +1 -1
  74. package/dist/public/node/cli.d.ts +1 -1
  75. package/dist/public/node/cli.js +0 -1
  76. package/dist/public/node/cli.js.map +1 -1
  77. package/dist/public/node/context/spin.d.ts +14 -0
  78. package/dist/public/node/context/spin.js +19 -0
  79. package/dist/public/node/context/spin.js.map +1 -1
  80. package/dist/public/node/environment.d.ts +12 -0
  81. package/dist/public/node/environment.js +14 -0
  82. package/dist/public/node/environment.js.map +1 -0
  83. package/dist/public/node/fs.d.ts +8 -12
  84. package/dist/public/node/fs.js +11 -35
  85. package/dist/public/node/fs.js.map +1 -1
  86. package/dist/public/node/http.js +5 -10
  87. package/dist/public/node/http.js.map +1 -1
  88. package/dist/public/node/local-storage.d.ts +37 -0
  89. package/dist/public/node/local-storage.js +44 -0
  90. package/dist/public/node/local-storage.js.map +1 -0
  91. package/dist/public/node/node-package-manager.js +1 -1
  92. package/dist/public/node/node-package-manager.js.map +1 -1
  93. package/dist/public/node/output.d.ts +0 -1
  94. package/dist/public/node/output.js +1 -2
  95. package/dist/public/node/output.js.map +1 -1
  96. package/dist/public/node/path.d.ts +58 -2
  97. package/dist/public/node/path.js +75 -3
  98. package/dist/public/node/path.js.map +1 -1
  99. package/dist/public/node/ruby.d.ts +2 -1
  100. package/dist/public/node/ruby.js +85 -31
  101. package/dist/public/node/ruby.js.map +1 -1
  102. package/dist/public/node/themes/conf.d.ts +12 -0
  103. package/dist/public/node/themes/conf.js +24 -0
  104. package/dist/public/node/themes/conf.js.map +1 -0
  105. package/dist/public/node/themes/theme-manager.js +1 -1
  106. package/dist/public/node/themes/theme-manager.js.map +1 -1
  107. package/dist/public/node/themes/themes-api.js +3 -3
  108. package/dist/public/node/themes/themes-api.js.map +1 -1
  109. package/dist/tsconfig.tsbuildinfo +1 -1
  110. package/package.json +9 -8
  111. package/dist/private/node/secure-store.d.ts +0 -19
  112. package/dist/private/node/secure-store.js +0 -63
  113. package/dist/private/node/secure-store.js.map +0 -1
  114. package/dist/public/node/colors.js.map +0 -1
  115. package/dist/public/node/conf.d.ts +0 -2
  116. package/dist/public/node/conf.js +0 -3
  117. package/dist/public/node/conf.js.map +0 -1
  118. package/dist/public/node/semver.js.map +0 -1
  119. package/dist/public/node/themes/generate-theme-name.js.map +0 -1
  120. package/dist/public/node/themes/replace-invalid-characters.js.map +0 -1
  121. package/dist/public/node/themes/themes-api/headers.js.map +0 -1
  122. package/dist/public/node/themes/themes-api/retry.js.map +0 -1
  123. package/dist/public/node/themes/themes-api/throttler.js.map +0 -1
  124. package/dist/store/schema.d.ts +0 -3
  125. package/dist/store/schema.js +0 -27
  126. package/dist/store/schema.js.map +0 -1
  127. /package/dist/{public → private}/node/colors.d.ts +0 -0
  128. /package/dist/{public → private}/node/colors.js +0 -0
  129. /package/dist/{public → private}/node/semver.d.ts +0 -0
  130. /package/dist/{public → private}/node/semver.js +0 -0
  131. /package/dist/{public → private}/node/themes/generate-theme-name.d.ts +0 -0
  132. /package/dist/{public → private}/node/themes/generate-theme-name.js +0 -0
  133. /package/dist/{public → private}/node/themes/replace-invalid-characters.d.ts +0 -0
  134. /package/dist/{public → private}/node/themes/replace-invalid-characters.js +0 -0
  135. /package/dist/{public → private}/node/themes/themes-api/headers.d.ts +0 -0
  136. /package/dist/{public → private}/node/themes/themes-api/headers.js +0 -0
  137. /package/dist/{public → private}/node/themes/themes-api/retry.d.ts +0 -0
  138. /package/dist/{public → private}/node/themes/themes-api/retry.js +0 -0
  139. /package/dist/{public → private}/node/themes/themes-api/throttler.d.ts +0 -0
  140. /package/dist/{public → private}/node/themes/themes-api/throttler.js +0 -0
package/README.md CHANGED
@@ -6,8 +6,8 @@
6
6
  <a href="https://github.com/Shopify/cli/actions/workflows/shopify-cli.yml">![badge](https://github.com/Shopify/cli/actions/workflows/shopify-cli.yml/badge.svg)</a>
7
7
 
8
8
  With the Shopify command line interface (Shopify CLI 3.0), you can:
9
+ - initialize, build, dev, and deploy Shopify apps, extensions, functions and themes
9
10
  - build custom storefronts and manage their hosting
10
- - initialize, build, dev, and deploy Shopify apps — and generate app extensions
11
11
 
12
12
  <p>&nbsp;</p>
13
13
 
@@ -30,15 +30,23 @@ Learn more in the docs: [Create an app](https://shopify.dev/apps/getting-started
30
30
 
31
31
  <p>&nbsp;</p>
32
32
 
33
+ ## Developing themes with Shopify CLI
34
+
35
+ To work with themes, the CLI needs to be installed globally with:
36
+
37
+ - `npm install -g @shopify/cli @shopify/theme`
38
+
39
+ You can also use do it through Homebrew on macOS: `brew install shopify-cli`
40
+
41
+ Learn more in the docs: [Shopify CLI for themes](https://shopify.dev/docs/themes/tools/cli)
42
+
43
+ <p>&nbsp;</p>
44
+
33
45
  ## Developing Hydrogen custom storefronts with Shopify CLI ##
34
46
 
35
- When you’re building a custom storefront, use Hydrogen, Shopify’s React-based framework optimized for headless commerce. Initialize a new Hydrogen app with a fully-featured Demo Store template, or start from scratch with the minimal Hello World template. Shopify Plus stores can deploy their Hydrogen apps to Oxygen, Shopify’s global hosting solution, at no extra cost.
47
+ The Hydrogen code lives here: https://github.com/Shopify/hydrogen/tree/main/packages/cli
36
48
 
37
- Get started using one of the following commands:
38
- - `npm init @shopify/hydrogen@latest`
39
- - `npx @shopify/create-hydrogen@latest`
40
- - `pnpm create @shopify/create-hydrogen@latest`
41
- - `yarn create @shopify/hydrogen`
49
+ Learn more in the docs: [Shopify CLI for Hydrogen storefronts](https://shopify.dev/docs/custom-storefronts/hydrogen/cli)
42
50
 
43
51
  <p>&nbsp;</p>
44
52
 
@@ -1,13 +1,14 @@
1
1
  # NOTE: These are development-only dependencies
2
2
  source "https://rubygems.org"
3
3
 
4
- gemspec
4
+ gem "bugsnag", "~> 6.22"
5
+ gem "listen", "~> 3.7.0"
6
+ gem "theme-check", "~> 1.14.0"
5
7
 
6
8
  # None of these can actually be used in a development copy of dev
7
9
  # They are all for CI and tests
8
10
  # `dev` uses no gems
9
11
  group :development, :test do
10
- gem "rake"
11
12
  gem "pry-byebug"
12
13
  gem "byebug"
13
14
  gem "rubocop-shopify", require: false
@@ -16,11 +17,13 @@ group :development, :test do
16
17
  gem "iniparse", "~> 1.5"
17
18
  gem "colorize", "~> 0.8.1"
18
19
  gem "octokit", "~> 4.0"
20
+ gem "bundler", ">= 2.3.11"
21
+ gem "rake", "~> 12.3", ">= 12.3.3"
22
+ gem "minitest", "~> 5.0"
19
23
  end
20
24
 
21
25
  group :test do
22
26
  gem "mocha", require: false
23
- gem "minitest", ">= 5.0.0", require: false
24
27
  gem "minitest-reporters", require: false
25
28
  gem "minitest-fail-fast", require: false
26
29
  gem "fakefs", ">= 1.0", require: false
@@ -20,6 +20,9 @@ module Extension
20
20
  parser.on("-T", "--theme=NAME_OR_ID", "Theme ID or name of the theme app extension host theme.") do |theme|
21
21
  flags[:theme] = theme
22
22
  end
23
+ parser.on("--generate-tmp-theme", "Populate host theme, created by CLI 3, with assets") do |generate_tmp_theme|
24
+ flags[:generate_tmp_theme] = generate_tmp_theme
25
+ end
23
26
  parser.on("--api-key=API_KEY", "Connect your extension and app by inserting your app's API key") do |api_key|
24
27
  flags[:api_key] = api_key.gsub('"', "")
25
28
  end
@@ -45,6 +48,7 @@ module Extension
45
48
  property! :tunnel_requested, accepts: [true, false], reader: :tunnel_requested?, default: true
46
49
  property :port, accepts: (1...(2**16))
47
50
  property :theme, accepts: String, default: nil
51
+ property :generate_tmp_theme, accepts: [true, false], reader: :generate_tmp_theme?, default: false
48
52
  property :api_key, accepts: String, default: nil
49
53
  property :api_secret, accepts: String, default: nil
50
54
  property :registration_id, accepts: String, default: nil
@@ -60,6 +64,7 @@ module Extension
60
64
  resource_url: options.flags[:resource_url],
61
65
  port: options.flags[:port],
62
66
  theme: options.flags[:theme],
67
+ generate_tmp_theme: generate_tmp_theme?,
63
68
  api_key: options.flags[:api_key],
64
69
  api_secret: options.flags[:api_secret],
65
70
  registration_id: options.flags[:registration_id],
@@ -103,6 +108,10 @@ module Extension
103
108
  tunnel.nil? || !!tunnel
104
109
  end
105
110
 
111
+ def generate_tmp_theme?
112
+ options.flags[:generate_tmp_theme] == true
113
+ end
114
+
106
115
  def find_available_port(runtime_configuration)
107
116
  return runtime_configuration unless runtime_configuration.port.nil?
108
117
  return runtime_configuration unless specification_handler.choose_port?(@ctx)
@@ -137,6 +146,7 @@ module Extension
137
146
  tunnel_url: runtime_configuration.tunnel_url,
138
147
  port: runtime_configuration.port,
139
148
  theme: runtime_configuration.theme,
149
+ generate_tmp_theme: runtime_configuration.generate_tmp_theme?,
140
150
  api_key: runtime_configuration.api_key,
141
151
  api_secret: runtime_configuration.api_secret,
142
152
  registration_id: runtime_configuration.registration_id,
@@ -78,7 +78,7 @@ module Extension
78
78
  root = options[:context]&.root
79
79
  project = options[:project]
80
80
  properties = options
81
- .slice(:port, :theme)
81
+ .slice(:port, :theme, :generate_tmp_theme)
82
82
  .compact
83
83
  .merge({
84
84
  project: project,
@@ -19,6 +19,7 @@ module Theme
19
19
  subcommand :List, "list", Project.project_filepath("commands/list")
20
20
  subcommand :Share, "share", Project.project_filepath("commands/share")
21
21
  subcommand :LanguageServer, "language-server", Project.project_filepath("commands/language_server")
22
+ subcommand :Console, "console", Project.project_filepath("commands/console")
22
23
  end
23
24
  ShopifyCLI::Commands.register("Theme::Command", "theme")
24
25
 
@@ -49,13 +49,14 @@ module Theme
49
49
  private
50
50
 
51
51
  def current_directory_confirmed?
52
- raise "Current theme directory can't be confirmed during tests" if @ctx.testing?
52
+ return true if options.flags[:force]
53
53
 
54
+ @ctx.warn(@ctx.message("theme.current_directory_is_not_theme_directory"))
54
55
  Forms::ConfirmStore.ask(
55
56
  @ctx,
56
57
  [],
57
58
  title: @ctx.message("theme.confirm_current_directory"),
58
- force: options.flags[:force],
59
+ force: !ShopifyCLI::Environment.interactive?,
59
60
  ).confirmed?
60
61
  end
61
62
 
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/theme/repl"
4
+
5
+ module Theme
6
+ class Command
7
+ class Console < ShopifyCLI::Command::SubCommand
8
+ recommend_default_ruby_range
9
+
10
+ def call(_args, *)
11
+ ShopifyCLI::Theme::Repl.new(@ctx).run
12
+ end
13
+ end
14
+ end
15
+ end
@@ -67,6 +67,7 @@ module Theme
67
67
  begin
68
68
  syncer.start_threads
69
69
  if options.flags[:json]
70
+ syncer.standard_reporter.disable!
70
71
  syncer.upload_theme!(delete: delete)
71
72
  else
72
73
  CLI::UI::Frame.open(@ctx.message("theme.push.info.pushing", theme.name, theme.id, theme.shop)) do
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Theme
3
4
  module Messages
4
5
  MESSAGES = {
@@ -18,8 +19,9 @@ module Theme
18
19
  ENSURE_USER
19
20
  stable_flag_suggestion: "If the current command isn't working as expected," \
20
21
  " we suggest re-running the command with the {{command: --stable}} flag",
21
- confirm_current_directory: "It doesn’t seem like you’re running this command in a theme directory. " \
22
- "Are you sure you want to proceed?",
22
+ current_directory_is_not_theme_directory: "It doesn’t seem like you’re running this command" \
23
+ " in a theme directory.",
24
+ confirm_current_directory: "Are you sure you want to proceed?",
23
25
  init: {
24
26
  help: <<~HELP,
25
27
  {{command:%s theme init}}: Clones a Git repository to use as a starting point for building a new theme.
@@ -43,6 +43,7 @@ module ShopifyCLI
43
43
  SPIN_WORKSPACE = "SPIN_WORKSPACE"
44
44
  SPIN_NAMESPACE = "SPIN_NAMESPACE"
45
45
  SPIN_HOST = "SPIN_HOST"
46
+ SPIN_FQDN = "SPIN_FQDN"
46
47
 
47
48
  # Deprecated, equivalent to using SPIN=1
48
49
  SPIN_PARTNERS = "SHOPIFY_APP_CLI_SPIN_PARTNERS"
@@ -109,6 +109,10 @@ module ShopifyCLI
109
109
  end
110
110
 
111
111
  def self.spin_url_override(env_variables: ENV)
112
+ return env_variables[Constants::EnvironmentVariables::SPIN_FQDN] if env_variables.key?(
113
+ Constants::EnvironmentVariables::SPIN_FQDN
114
+ )
115
+
112
116
  tokens = SPIN_OVERRIDE_ENV_NAMES.map do |name|
113
117
  env_variables[name]
114
118
  end
@@ -25,7 +25,7 @@ module ShopifyCLI
25
25
  "</script>",
26
26
  ].join("\n")
27
27
 
28
- body = body.join.gsub("</body>", "#{hot_reload_script}\n</body>")
28
+ body = body.join.sub("</body>", "#{hot_reload_script}\n</body>")
29
29
 
30
30
  [body]
31
31
  end
@@ -16,12 +16,15 @@ module ShopifyCLI
16
16
  end
17
17
 
18
18
  def call(env)
19
- if env["PATH_INFO"] == "/hot-reload"
19
+ path = env["PATH_INFO"]
20
+ if path == "/hot-reload"
20
21
  create_stream
21
22
  else
22
23
  status, headers, body = @app.call(env)
23
24
 
24
- body = inject_hot_reload_javascript(body) if request_is_html?(headers)
25
+ if request_is_html?(headers) && leads_to_injectable_body?(path)
26
+ body = inject_hot_reload_javascript(body)
27
+ end
25
28
 
26
29
  [status, headers, body]
27
30
  end
@@ -43,6 +46,10 @@ module ShopifyCLI
43
46
  headers["content-type"]&.start_with?("text/html")
44
47
  end
45
48
 
49
+ def leads_to_injectable_body?(path)
50
+ path !~ /web-pixels-manager.+sandbox/
51
+ end
52
+
46
53
  def inject_hot_reload_javascript(body)
47
54
  @script_injector&.inject(body: body, dir: __dir__, mode: @mode)
48
55
  end
@@ -78,6 +78,17 @@ module ShopifyCLI
78
78
  [response.code, headers, body]
79
79
  end
80
80
 
81
+ def secure_session_id
82
+ if secure_session_id_expired?
83
+ @ctx.debug("Refreshing preview _secure_session_id cookie")
84
+ response = request("HEAD", "/", query: [[:preview_theme_id, theme_id]])
85
+ @secure_session_id = extract_secure_session_id_from_response_headers(response)
86
+ @last_session_cookie_refresh = Time.now
87
+ end
88
+
89
+ @secure_session_id
90
+ end
91
+
81
92
  private
82
93
 
83
94
  def patch_body(env, body)
@@ -164,17 +175,6 @@ module ShopifyCLI
164
175
  headers["set-cookie"][SESSION_COOKIE_REGEXP, 1]
165
176
  end
166
177
 
167
- def secure_session_id
168
- if secure_session_id_expired?
169
- @ctx.debug("Refreshing preview _secure_session_id cookie")
170
- response = request("HEAD", "/", query: [[:preview_theme_id, theme_id]])
171
- @secure_session_id = extract_secure_session_id_from_response_headers(response)
172
- @last_session_cookie_refresh = Time.now
173
- end
174
-
175
- @secure_session_id
176
- end
177
-
178
178
  def get_response_headers(response, env)
179
179
  response_headers = normalize_headers(
180
180
  response.respond_to?(:headers) ? response.headers : response.to_hash,
@@ -184,7 +184,8 @@ module ShopifyCLI
184
184
  # (Taken from Rack::Proxy)
185
185
  response_headers.reject! { |k| HOP_BY_HOP_HEADERS.include?(k.downcase) }
186
186
 
187
- if response_headers["location"]&.include?("myshopify.com")
187
+ if response_headers["location"]&.include?("myshopify.com") ||
188
+ response_headers["location"]&.include?("spin.dev")
188
189
  response_headers["location"].gsub!(%r{(https://#{shop})}, "http://#{host(env)}")
189
190
  end
190
191
 
@@ -139,8 +139,8 @@ module ShopifyCLI
139
139
  private
140
140
 
141
141
  def setup_server
142
- watcher.start
143
- remote_watcher.start if editor_sync
142
+ watcher&.start
143
+ remote_watcher&.start if editor_sync
144
144
  end
145
145
 
146
146
  def teardown_server
@@ -26,12 +26,13 @@ module ShopifyCLI
26
26
  # Extensions
27
27
  ScriptInjector = ShopifyCLI::Theme::Extension::DevServer::HotReload::ScriptInjector
28
28
 
29
- attr_accessor :project, :specification_handler
29
+ attr_accessor :project, :specification_handler, :generate_tmp_theme
30
30
 
31
31
  class << self
32
- def start(ctx, root, port: 9292, theme: nil, project:, specification_handler:)
32
+ def start(ctx, root, port: 9292, theme: nil, generate_tmp_theme: false, project:, specification_handler:)
33
33
  instance.project = project
34
34
  instance.specification_handler = specification_handler
35
+ instance.generate_tmp_theme = generate_tmp_theme
35
36
 
36
37
  super(ctx, root, port: port, theme: theme)
37
38
  end
@@ -66,8 +67,10 @@ module ShopifyCLI
66
67
 
67
68
  def theme
68
69
  @theme ||= if theme_identifier
69
- theme = ShopifyCLI::Theme::Theme.find_by_identifier(ctx, identifier: theme_identifier)
70
- theme || ctx.abort(not_found_error_message)
70
+ theme = HostTheme.find_by_identifier(ctx, identifier: theme_identifier)
71
+ ctx.abort(not_found_error_message) unless theme
72
+ theme.generate_tmp_theme if generate_tmp_theme
73
+ theme
71
74
  else
72
75
  HostTheme.find_or_create!(ctx)
73
76
  end
@@ -61,20 +61,9 @@ module ShopifyCLI
61
61
  new(ctx, root: nil).ensure_exists!
62
62
  end
63
63
 
64
- private
65
-
66
- def generate_host_theme_name
67
- hostname = Socket.gethostname.split(".").shift
68
- hash = SecureRandom.hex(3)
69
-
70
- theme_name = "App Ext. Host ()"
71
- hostname_character_limit = API_NAME_LIMIT - theme_name.length - hash.length - 1
72
- identifier = encode_identifier("#{hash}-#{hostname[0, hostname_character_limit]}")
73
- theme_name = "App Ext. Host (#{identifier})"
74
-
75
- ShopifyCLI::DB.set(host_theme_name: theme_name)
76
-
77
- theme_name
64
+ def self.find_by_identifier(ctx, root: nil, identifier:)
65
+ ShopifyCLI::DB.set(host_theme_id: identifier)
66
+ find(ctx, root: root)
78
67
  end
79
68
 
80
69
  def generate_tmp_theme
@@ -98,6 +87,22 @@ module ShopifyCLI
98
87
  end
99
88
  end
100
89
  end
90
+
91
+ private
92
+
93
+ def generate_host_theme_name
94
+ hostname = Socket.gethostname.split(".").shift
95
+ hash = SecureRandom.hex(3)
96
+
97
+ theme_name = "App Ext. Host ()"
98
+ hostname_character_limit = API_NAME_LIMIT - theme_name.length - hash.length - 1
99
+ identifier = encode_identifier("#{hash}-#{hostname[0, hostname_character_limit]}")
100
+ theme_name = "App Ext. Host (#{identifier})"
101
+
102
+ ShopifyCLI::DB.set(host_theme_name: theme_name)
103
+
104
+ theme_name
105
+ end
101
106
  end
102
107
  end
103
108
  end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ class Repl
6
+ class Api
7
+ attr_reader :ctx, :repl
8
+
9
+ def initialize(ctx, repl)
10
+ @ctx = ctx
11
+ @repl = repl
12
+ end
13
+
14
+ def request(liquid_snippet)
15
+ Net::HTTP.start(uri.host, 443, use_ssl: true) do |http|
16
+ req = Net::HTTP::Post.new(uri)
17
+
18
+ req.initialize_http_header(headers)
19
+ req.set_form_data(form_data(liquid_snippet))
20
+ res = http.request(req)
21
+
22
+ debug(res)
23
+
24
+ res
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def debug(response)
31
+ return response unless debug?
32
+
33
+ ctx.debug(<<~DEBUG)
34
+ URI: #{uri}
35
+ ---
36
+ HTTP status: #{response.code}
37
+ ---
38
+ Response body:
39
+ #{response.body}
40
+ DEBUG
41
+
42
+ response
43
+ end
44
+
45
+ def debug?
46
+ @is_debug ||= ctx.debug?
47
+ end
48
+
49
+ def form_data(liquid_snippet)
50
+ template = ["", "", liquid_snippet, "", liquid_template].join("\n")
51
+
52
+ {
53
+ "replace_templates[sections/announcement-bar.liquid]" => template,
54
+ :_method => "GET",
55
+ }
56
+ end
57
+
58
+ def liquid_template
59
+ @liquid_template ||= ::File.read("#{__dir__}/resources/template.liquid")
60
+ end
61
+
62
+ def cookie
63
+ @cookie ||= "storefront_digest=#{repl.storefront_digest}; _secure_session_id=#{repl.secure_session_id}"
64
+ end
65
+
66
+ def shop
67
+ @shop ||= ShopifyCLI::Theme::ThemeAdminAPI.new(ctx).get_shop_or_abort
68
+ end
69
+
70
+ def storefront_renderer_token
71
+ @storefront_renderer_token ||= ShopifyCLI::Environment.storefront_renderer_auth_token ||
72
+ ShopifyCLI::DB.get(:storefront_renderer_production_exchange_token)
73
+ end
74
+
75
+ def headers
76
+ @headers ||= if Environment.theme_access_password?
77
+ {
78
+ "Cookie" => cookie,
79
+ "X-Shopify-Access-Token" => Environment.admin_auth_token,
80
+ "X-Shopify-Shop" => shop,
81
+ }
82
+ else
83
+ {
84
+ "Cookie" => cookie,
85
+ "Authorization" => "Bearer #{storefront_renderer_token}",
86
+ "User-Agent" => "Shopify CLI",
87
+ }
88
+ end
89
+ end
90
+
91
+ def uri
92
+ return @api_uri if @api_uri
93
+
94
+ uri_address = if Environment.theme_access_password?
95
+ "https://#{ThemeAccessAPI::BASE_URL}/cli/sfr"
96
+ else
97
+ "https://#{shop}"
98
+ end
99
+
100
+ @api_uri = URI(uri_address)
101
+ @api_uri.query = URI.encode_www_form([["section_id", "announcement-bar"], [:_fd, 0], [:pb, 0]])
102
+ @api_uri
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ class Repl
6
+ class AuthDevServer < ShopifyCLI::Theme::DevServer
7
+ attr_accessor :app, :repl
8
+
9
+ REPL_THEME = "liquid-console-repl"
10
+
11
+ class << self
12
+ def start(ctx, repl, port)
13
+ instance.repl = repl
14
+
15
+ super(ctx, nil, port: port, theme: REPL_THEME)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def theme
22
+ @theme ||= find_repl_theme || create_repl_theme
23
+ end
24
+
25
+ def find_repl_theme
26
+ Theme.find(ctx, nil) do |theme_hash|
27
+ theme_hash["name"] == theme_identifier && theme_hash["role"] == "development"
28
+ end
29
+ end
30
+
31
+ def create_repl_theme
32
+ repl_theme = Theme.new(ctx, name: theme_identifier, role: "development")
33
+ repl_theme.create
34
+
35
+ api_client = ThemeAdminAPI.new(ctx, repl_theme.shop)
36
+ status, _body, _response = api_client.put(
37
+ path: "themes/#{repl_theme.id}/assets/bulk.json",
38
+ method: "PUT",
39
+ body: JSON.generate({
40
+ assets: [
41
+ { key: "sections/announcement-bar.liquid", value: "" },
42
+ { key: "config/settings_schema.json", value: "[]" },
43
+ { key: "config/settings_data.json", value: "{}" },
44
+ { key: "layout/theme.liquid", value: "{{ content_for_header }}{{ content_for_layout }}" },
45
+ { key: "layout/password.liquid", value: "{{ content_for_header }}{{ content_for_layout }}" },
46
+ ],
47
+ }),
48
+ )
49
+
50
+ if status != 207
51
+ ctx.puts("{{red:Shopify Liquid console could not be initiatilize (HTTP status: #{status})}}")
52
+ raise ShopifyCLI::AbortSilent
53
+ end
54
+
55
+ repl_theme
56
+ end
57
+
58
+ def middleware_stack
59
+ @app = proxy
60
+ @app = CdnFonts.new(app, theme: theme)
61
+ @app = AuthMiddleware.new(app, proxy, repl) { WebServer.shutdown }
62
+ end
63
+
64
+ def param_builder
65
+ @param_builder ||= ProxyParamBuilder.new
66
+ end
67
+
68
+ def proxy
69
+ @proxy ||= Proxy.new(ctx, theme, param_builder)
70
+ end
71
+
72
+ def frame_title; end
73
+ def preview_message; end
74
+ def setup_server; end
75
+ def stop(_signal); end
76
+ def sync_theme; end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ class Repl
6
+ class AuthMiddleware
7
+ def initialize(app, proxy, repl, &stop_dev_server)
8
+ @app = app
9
+ @proxy = proxy
10
+ @repl = repl
11
+ @stop_dev_server = stop_dev_server
12
+ end
13
+
14
+ def call(env)
15
+ @env = env
16
+
17
+ return @app.call(env) unless authenticated?
18
+
19
+ authenticate!
20
+ shutdown
21
+
22
+ [
23
+ 200,
24
+ {
25
+ "Content-Type" => "text/html",
26
+ "Content-Length" => success_body.size.to_s,
27
+ },
28
+ [success_body],
29
+ ]
30
+ end
31
+
32
+ def close
33
+ @app.close
34
+ end
35
+
36
+ private
37
+
38
+ def storefront_session
39
+ cookie["storefront_digest"]&.first
40
+ end
41
+
42
+ def secure_session
43
+ @proxy.secure_session_id
44
+ end
45
+
46
+ def authenticated?
47
+ storefront_session && secure_session
48
+ end
49
+
50
+ def authenticate!
51
+ @repl.authenticate(storefront_session, secure_session)
52
+ end
53
+
54
+ def shutdown
55
+ Thread.new do
56
+ # Web server answers the request and shutdown itself
57
+ sleep(1)
58
+
59
+ @stop_dev_server.call
60
+ end
61
+ end
62
+
63
+ def success_body
64
+ @success_body ||= ::File.read("#{__dir__}/resources/success.html")
65
+ end
66
+
67
+ def cookie
68
+ CGI::Cookie.parse(@env["HTTP_COOKIE"])
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end