shopify-cli 2.7.2 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +2 -2
  3. data/.github/workflows/shopify.yml +1 -1
  4. data/.gitignore +1 -0
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +52 -0
  7. data/Codespace.dockerfile +2 -2
  8. data/Gemfile.lock +4 -4
  9. data/RELEASING.md +4 -3
  10. data/Tests.dockerfile +2 -2
  11. data/dev.yml +3 -3
  12. data/ext/javy/hashes/javy-arm-macos-v0.1.0.gz.sha256 +1 -0
  13. data/ext/javy/hashes/javy-x86_64-linux-v0.1.0.gz.sha256 +1 -0
  14. data/ext/javy/hashes/javy-x86_64-macos-v0.1.0.gz.sha256 +1 -0
  15. data/ext/javy/hashes/javy-x86_64-windows-v0.1.0.gz.sha256 +1 -0
  16. data/ext/javy/javy.rb +31 -13
  17. data/lib/graphql/get_extension_registrations.graphql +27 -0
  18. data/lib/project_types/extension/cli.rb +27 -2
  19. data/lib/project_types/extension/commands/build.rb +10 -10
  20. data/lib/project_types/extension/commands/create.rb +2 -3
  21. data/lib/project_types/extension/commands/push.rb +36 -8
  22. data/lib/project_types/extension/extension_project.rb +1 -1
  23. data/lib/project_types/extension/features/argo_serve.rb +6 -5
  24. data/lib/project_types/extension/forms/questions/ask_registration.rb +6 -2
  25. data/lib/project_types/extension/loaders/project.rb +29 -0
  26. data/lib/project_types/extension/loaders/specification_handler.rb +22 -0
  27. data/lib/project_types/extension/messages/messages.rb +4 -0
  28. data/lib/project_types/extension/models/app.rb +1 -1
  29. data/lib/project_types/extension/models/development_server.rb +2 -4
  30. data/lib/project_types/extension/models/specification_handlers/default.rb +4 -0
  31. data/lib/project_types/extension/tasks/convert_server_config.rb +3 -1
  32. data/lib/project_types/extension/tasks/execute_commands/base.rb +13 -0
  33. data/lib/project_types/extension/tasks/execute_commands/build.rb +29 -0
  34. data/lib/project_types/extension/tasks/execute_commands/create.rb +33 -0
  35. data/lib/project_types/extension/tasks/execute_commands/serve.rb +35 -0
  36. data/lib/project_types/extension/tasks/merge_server_config.rb +33 -22
  37. data/lib/project_types/rails/commands/create.rb +1 -1
  38. data/lib/project_types/rails/gem.rb +1 -2
  39. data/lib/project_types/script/cli.rb +8 -1
  40. data/lib/project_types/script/commands/connect.rb +19 -0
  41. data/lib/project_types/script/commands/create.rb +8 -2
  42. data/lib/project_types/script/commands/push.rb +35 -12
  43. data/lib/project_types/script/config/extension_points.yml +10 -2
  44. data/lib/project_types/script/graphql/app_script_set.graphql +2 -2
  45. data/lib/project_types/script/layers/application/connect_app.rb +15 -3
  46. data/lib/project_types/script/layers/application/create_script.rb +17 -17
  47. data/lib/project_types/script/layers/application/extension_points.rb +50 -26
  48. data/lib/project_types/script/layers/application/push_script.rb +6 -3
  49. data/lib/project_types/script/layers/domain/errors.rb +1 -4
  50. data/lib/project_types/script/layers/domain/extension_point.rb +14 -0
  51. data/lib/project_types/script/layers/domain/push_package.rb +3 -3
  52. data/lib/project_types/script/layers/domain/{script_json.rb → script_config.rb} +2 -2
  53. data/lib/project_types/script/layers/domain/script_project.rb +1 -1
  54. data/lib/project_types/script/layers/infrastructure/errors.rb +30 -5
  55. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +2 -2
  56. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +125 -27
  57. data/lib/project_types/script/layers/infrastructure/script_service.rb +11 -11
  58. data/lib/project_types/script/loaders/project.rb +44 -0
  59. data/lib/project_types/script/loaders/specification_handler.rb +22 -0
  60. data/lib/project_types/script/messages/messages.rb +34 -3
  61. data/lib/project_types/script/ui/error_handler.rb +35 -15
  62. data/lib/project_types/theme/commands/pull.rb +39 -16
  63. data/lib/project_types/theme/commands/push.rb +60 -29
  64. data/lib/project_types/theme/commands/serve.rb +5 -0
  65. data/lib/project_types/theme/messages/messages.rb +30 -18
  66. data/lib/shopify_cli/command.rb +6 -0
  67. data/lib/shopify_cli/commands/login.rb +11 -5
  68. data/lib/shopify_cli/commands/switch.rb +1 -1
  69. data/lib/shopify_cli/constants.rb +5 -0
  70. data/lib/shopify_cli/context.rb +66 -11
  71. data/lib/shopify_cli/environment.rb +15 -4
  72. data/lib/shopify_cli/form.rb +2 -0
  73. data/lib/shopify_cli/git.rb +2 -0
  74. data/lib/shopify_cli/identity_auth.rb +1 -0
  75. data/lib/shopify_cli/messages/messages.rb +10 -2
  76. data/lib/shopify_cli/partners_api/app_extensions/job.rb +36 -0
  77. data/lib/shopify_cli/partners_api/app_extensions.rb +46 -0
  78. data/lib/shopify_cli/partners_api/organizations.rb +2 -5
  79. data/lib/shopify_cli/partners_api.rb +1 -0
  80. data/lib/shopify_cli/project.rb +8 -7
  81. data/lib/shopify_cli/resources/env_file.rb +18 -6
  82. data/lib/shopify_cli/services/app/create/rails_service.rb +1 -1
  83. data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +73 -0
  84. data/lib/shopify_cli/theme/dev_server/hot-reload.js +57 -10
  85. data/lib/shopify_cli/theme/dev_server/hot_reload.rb +18 -2
  86. data/lib/shopify_cli/theme/dev_server/proxy/template_param_builder.rb +84 -0
  87. data/lib/shopify_cli/theme/dev_server/proxy.rb +10 -15
  88. data/lib/shopify_cli/theme/dev_server/reload_mode.rb +34 -0
  89. data/lib/shopify_cli/theme/dev_server.rb +8 -21
  90. data/lib/shopify_cli/theme/theme.rb +26 -4
  91. data/lib/shopify_cli/thread_pool/job.rb +27 -0
  92. data/lib/shopify_cli/thread_pool.rb +37 -0
  93. data/lib/shopify_cli/tunnel.rb +1 -0
  94. data/lib/shopify_cli/version.rb +1 -1
  95. data/lib/shopify_cli.rb +4 -0
  96. data/shopify-cli.gemspec +1 -1
  97. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +3 -1
  98. metadata +26 -7
  99. data/lib/graphql/all_orgs_with_extensions.graphql +0 -37
  100. data/lib/project_types/extension/tasks/run_extension_command.rb +0 -82
@@ -8,6 +8,8 @@ module ShopifyCLI
8
8
  def available?(ctx)
9
9
  _output, status = ctx.capture2e("git", "status")
10
10
  status.success?
11
+ rescue Errno::ENOENT # git is not installed
12
+ false
11
13
  end
12
14
 
13
15
  ##
@@ -229,6 +229,7 @@ module ShopifyCLI
229
229
  uri = URI.parse("#{auth_url}#{endpoint}")
230
230
  https = Net::HTTP.new(uri.host, uri.port)
231
231
  https.use_ssl = true
232
+ https.verify_mode = OpenSSL::SSL::VERIFY_NONE if ENV["SSL_VERIFY_NONE"]
232
233
  request = Net::HTTP::Post.new(uri.path)
233
234
  request["User-Agent"] = "Shopify CLI #{::ShopifyCLI::VERSION}"
234
235
  request.body = URI.encode_www_form(params)
@@ -14,6 +14,12 @@ module ShopifyCLI
14
14
  },
15
15
  },
16
16
  core: {
17
+ errors: {
18
+ option_parser: {
19
+ invalid_option: "The option {{command:%s}} is not supported.",
20
+ missing_argument: "The required argument {{command:%s}} is missing.",
21
+ },
22
+ },
17
23
  app: {
18
24
  help: <<~HELP,
19
25
  Suite of commands for developing apps. See {{command:%1$s app <command> --help}} for usage of each command.
@@ -409,7 +415,7 @@ module ShopifyCLI
409
415
  Usage: {{command:%s login [--store=STORE]}}
410
416
  HELP
411
417
  invalid_shop: <<~MESSAGE,
412
- Invalid store provided (%s). Please provide the store in the following format: my-store.myshopify.com
418
+ Invalid store provided (%s). Please make sure that the store belongs to your partner organization, and provide the store in the following format: my-store.myshopify.com
413
419
  MESSAGE
414
420
  shop_prompt: <<~PROMPT,
415
421
  What store are you connecting to? (e.g. my-store.myshopify.com; do {{bold:NOT}} include protocol part, e.g., https://)
@@ -719,7 +725,7 @@ module ShopifyCLI
719
725
  signup_suggestion: <<~MESSAGE,
720
726
  {{*}} To avoid tunnels that timeout, it is recommended to signup for a free ngrok
721
727
  account at {{underline:https://ngrok.com/signup}}. After you signup, install your
722
- personalized authorization token using {{command:%s [ node | rails ] tunnel auth <token>}}.
728
+ personalized authorization token using {{command:%s app tunnel auth <token>}}.
723
729
  MESSAGE
724
730
  start: "{{v}} ngrok tunnel running at {{underline:%s}}",
725
731
  start_with_account: "{{v}} ngrok tunnel running at {{underline:%s}}, with account %s",
@@ -784,6 +790,8 @@ module ShopifyCLI
784
790
  logged_in_partner_only: "Logged into partner organization {{green:%s}}",
785
791
  logged_in_partner_and_shop: "Logged into store {{green:%s}} in partner organization {{green:%s}}",
786
792
  },
793
+ error: "Error",
794
+ try_this: "Try this",
787
795
  },
788
796
  }.freeze
789
797
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/thread_pool/job"
4
+
5
+ module ShopifyCLI
6
+ class PartnersAPI
7
+ class AppExtensions
8
+ class Job < ShopifyCLI::ThreadPool::Job
9
+ attr_reader :result
10
+
11
+ def initialize(ctx, app, type)
12
+ super()
13
+ @ctx = ctx
14
+ @app = app
15
+ @api_key = @app["apiKey"]
16
+ @type = type
17
+ end
18
+
19
+ def perform!
20
+ resp = PartnersAPI.query(@ctx, "get_extension_registrations", **params)
21
+ @result = resp&.dig("data", "app") || {}
22
+ end
23
+
24
+ def patch_app_with_extensions!
25
+ @app.merge!(result)
26
+ end
27
+
28
+ private
29
+
30
+ def params
31
+ { api_key: @api_key, type: @type }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/thread_pool"
4
+
5
+ require_relative "app_extensions/job"
6
+
7
+ module ShopifyCLI
8
+ class PartnersAPI
9
+ class AppExtensions
10
+ class << self
11
+ def fetch_apps_extensions(ctx, orgs, type)
12
+ jobs = apps(orgs).map { |app| AppExtensions::Job.new(ctx, app, type) }
13
+
14
+ consume_jobs!(jobs)
15
+ patch_apps_with_extensions!(jobs)
16
+
17
+ orgs
18
+ end
19
+
20
+ private
21
+
22
+ def apps(orgs)
23
+ orgs.flat_map { |org| org["apps"] }
24
+ end
25
+
26
+ def consume_jobs!(jobs)
27
+ thread_pool = ShopifyCLI::ThreadPool.new
28
+ jobs.each do |job|
29
+ thread_pool.schedule(job)
30
+ end
31
+ thread_pool.shutdown
32
+
33
+ raise_if_any_error(jobs)
34
+ end
35
+
36
+ def patch_apps_with_extensions!(jobs)
37
+ jobs.each(&:patch_app_with_extensions!)
38
+ end
39
+
40
+ def raise_if_any_error(jobs)
41
+ jobs.find(&:error?).tap { |job| raise job.error if job }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -28,11 +28,8 @@ module ShopifyCLI
28
28
  end
29
29
 
30
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
31
+ orgs = fetch_with_app(ctx)
32
+ AppExtensions.fetch_apps_extensions(ctx, orgs, type)
36
33
  end
37
34
  end
38
35
  end
@@ -7,6 +7,7 @@ module ShopifyCLI
7
7
  #
8
8
  class PartnersAPI < API
9
9
  autoload :Organizations, "shopify_cli/partners_api/organizations"
10
+ autoload :AppExtensions, "shopify_cli/partners_api/app_extensions"
10
11
 
11
12
  class << self
12
13
  ##
@@ -107,13 +107,6 @@ module ShopifyCLI
107
107
  @dir = nil
108
108
  end
109
109
 
110
- private
111
-
112
- def directory(dir)
113
- @dir ||= Hash.new { |h, k| h[k] = __directory(k) }
114
- @dir[dir]
115
- end
116
-
117
110
  def at(dir)
118
111
  proj_dir = directory(dir)
119
112
  unless proj_dir
@@ -123,6 +116,13 @@ module ShopifyCLI
123
116
  @at[proj_dir]
124
117
  end
125
118
 
119
+ private
120
+
121
+ def directory(dir)
122
+ @dir ||= Hash.new { |h, k| h[k] = __directory(k) }
123
+ @dir[dir]
124
+ end
125
+
126
126
  def __directory(curr)
127
127
  loop do
128
128
  return nil if curr == "/" || /^[A-Z]:\/$/.match?(curr)
@@ -134,6 +134,7 @@ module ShopifyCLI
134
134
  end
135
135
 
136
136
  property :directory # :nodoc:
137
+ property :env # :nodoc:
137
138
 
138
139
  ##
139
140
  # will read, parse and return the envfile for the project
@@ -14,13 +14,21 @@ module ShopifyCLI
14
14
  }
15
15
 
16
16
  class << self
17
- def read(_directory = Dir.pwd)
18
- input = parse_external_env
17
+ def path(directory)
18
+ File.join(directory, FILENAME)
19
+ end
20
+
21
+ def read(_directory = Dir.pwd, overrides: {})
22
+ input = parse_external_env(overrides: overrides)
19
23
  new(input)
20
24
  end
21
25
 
26
+ def from_hash(hash)
27
+ new(env_input(hash))
28
+ end
29
+
22
30
  def parse(directory)
23
- File.read(File.join(directory, FILENAME))
31
+ File.read(path(directory))
24
32
  .gsub("\r\n", "\n").split("\n").each_with_object({}) do |line, output|
25
33
  match = /\A([A-Za-z_0-9]+)\s*=\s*(.*)\z/.match(line)
26
34
  if match
@@ -37,10 +45,14 @@ module ShopifyCLI
37
45
  end
38
46
  end
39
47
 
40
- def parse_external_env(directory = Dir.pwd)
48
+ def parse_external_env(directory = Dir.pwd, overrides: {})
49
+ env_input(parse(directory), overrides: overrides)
50
+ end
51
+
52
+ def env_input(parsed_source, overrides: {})
41
53
  env_details = {}
42
54
  extra = {}
43
- parse(directory).each do |key, value|
55
+ parsed_source.merge(overrides).each do |key, value|
44
56
  if KEY_MAP[key]
45
57
  env_details[KEY_MAP[key]] = value
46
58
  else
@@ -53,7 +65,7 @@ module ShopifyCLI
53
65
  end
54
66
 
55
67
  property :api_key, required: true
56
- property :secret, required: true
68
+ property :secret
57
69
  property :shop
58
70
  property :scopes
59
71
  property :host
@@ -159,10 +159,10 @@ module ShopifyCLI
159
159
 
160
160
  CLI::UI::Frame.open(context.message("core.app.create.rails.generating_app", name)) do
161
161
  new_command = %w(rails new)
162
+ new_command << name
162
163
  new_command += DEFAULT_RAILS_FLAGS
163
164
  new_command << "--database=#{db}"
164
165
  new_command += rails_opts.split unless rails_opts.nil?
165
- new_command << name
166
166
 
167
167
  syscall(new_command)
168
168
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ module DevServer
6
+ class CdnFonts
7
+ FONTS_PATH = "/fonts"
8
+ FONTS_CDN = "https://fonts.shopifycdn.com"
9
+ FONTS_REGEX = %r{#{FONTS_CDN}}
10
+
11
+ def initialize(app, theme:)
12
+ @app = app
13
+ @theme = theme
14
+ end
15
+
16
+ def call(env)
17
+ path = env["PATH_INFO"]
18
+
19
+ # Serve from fonts CDN
20
+ return serve_font(env) if path.start_with?(FONTS_PATH)
21
+
22
+ # Proxy the request, and replace the URLs in the response
23
+ status, headers, body = @app.call(env)
24
+ body = replace_font_urls(body)
25
+ [status, headers, body]
26
+ end
27
+
28
+ private
29
+
30
+ def serve_font(env)
31
+ parameters = %w(PATH_INFO QUERY_STRING REQUEST_METHOD rack.input)
32
+ path, query, method, body_stream = *env.slice(*parameters).values
33
+
34
+ uri = fonts_cdn_uri(path, query)
35
+
36
+ response = Net::HTTP.start(uri.host, 443, use_ssl: true) do |http|
37
+ req_class = Net::HTTP.const_get(method.capitalize)
38
+ req = req_class.new(uri)
39
+ req.initialize_http_header(fonts_cdn_headers)
40
+ req.body_stream = body_stream
41
+ http.request(req)
42
+ end
43
+
44
+ [
45
+ response.code.to_s,
46
+ {
47
+ "Content-Type" => response.content_type,
48
+ "Content-Length" => response.content_length.to_s,
49
+ },
50
+ [response.body],
51
+ ]
52
+ end
53
+
54
+ def fonts_cdn_headers
55
+ {
56
+ "Referer" => "https://#{@theme.shop}",
57
+ "Transfer-Encoding" => "chunked",
58
+ }
59
+ end
60
+
61
+ def fonts_cdn_uri(path, query)
62
+ uri = URI.join("#{FONTS_CDN}/", path.gsub(%r{^#{FONTS_PATH}\/}, ""))
63
+ uri.query = query.split("&").last
64
+ uri
65
+ end
66
+
67
+ def replace_font_urls(body)
68
+ [body.join.gsub(FONTS_REGEX, FONTS_PATH)]
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -15,21 +15,64 @@
15
15
  eventSource.onerror = () => eventSource.close();
16
16
  }
17
17
 
18
- connect();
18
+ function reloadMode() {
19
+ var namespace = window.__SHOPIFY_CLI_ENV__;
20
+ return namespace.mode;
21
+ }
22
+
23
+ function isFullPageReloadMode(){
24
+ return reloadMode() === "full-page";
25
+ }
26
+
27
+ function isReloadModeActive(){
28
+ return reloadMode() !== "off";
29
+ }
30
+
31
+ function isRefreshRequired(files) {
32
+ if (isFullPageReloadMode()) {
33
+ return true;
34
+ }
35
+ return files.some((file) => !isCssFile(file) && !isSectionFile(file));
36
+ }
37
+
38
+ function refreshFile(file) {
39
+ if (isCssFile(file)) {
40
+ reloadCssFile(file);
41
+ return;
42
+ }
43
+
44
+ if (isSectionFile(file)) {
45
+ reloadSection(file);
46
+ return;
47
+ }
48
+ }
49
+
50
+ function setHotReloadCookie(files) {
51
+ var date = new Date();
52
+
53
+ // Hot reload cookie expires in 3 seconds
54
+ date.setSeconds(date.getSeconds() + 3);
55
+
56
+ var sections = files.join(',');
57
+ var expires = date.toUTCString();
58
+
59
+ document.cookie = `hot_reload_sections=${sections}; expires=${expires}; path=/`;
60
+ }
61
+
62
+ function refreshPage(files) {
63
+ setHotReloadCookie(files);
64
+ console.log('[HotReload] Refreshing entire page');
65
+ window.location.reload();
66
+ }
19
67
 
20
68
  function handleUpdate(message) {
21
69
  var data = JSON.parse(message.data);
70
+ var modifiedFiles = data.modified;
22
71
 
23
- // Assume only one file is modified at a time
24
- var modified = data.modified[0];
25
-
26
- if (isCssFile(modified)) {
27
- reloadCssFile(modified)
28
- } else if (isSectionFile(modified)) {
29
- reloadSection(modified);
72
+ if (isRefreshRequired(modifiedFiles)) {
73
+ refreshPage(modifiedFiles);
30
74
  } else {
31
- console.log(`[HotReload] Refreshing entire page`);
32
- window.location.reload();
75
+ modifiedFiles.forEach(refreshFile);
33
76
  }
34
77
  }
35
78
 
@@ -90,4 +133,8 @@
90
133
  }
91
134
  }
92
135
  }
136
+
137
+ if (isReloadModeActive()) {
138
+ connect();
139
+ }
93
140
  })();
@@ -4,10 +4,11 @@ module ShopifyCLI
4
4
  module Theme
5
5
  module DevServer
6
6
  class HotReload
7
- def initialize(ctx, app, theme:, watcher:, ignore_filter: nil)
7
+ def initialize(ctx, app, theme:, watcher:, mode:, ignore_filter: nil)
8
8
  @ctx = ctx
9
9
  @app = app
10
10
  @theme = theme
11
+ @mode = mode
11
12
  @streams = SSE::Streams.new
12
13
  @watcher = watcher
13
14
  @watcher.add_observer(self, :notify_streams_of_file_change)
@@ -48,12 +49,27 @@ module ShopifyCLI
48
49
 
49
50
  def inject_hot_reload_javascript(body)
50
51
  hot_reload_js = ::File.read("#{__dir__}/hot-reload.js")
51
- hot_reload_script = "<script>\n#{hot_reload_js}</script>"
52
+ hot_reload_script = [
53
+ "<script>",
54
+ params_js,
55
+ hot_reload_js,
56
+ "</script>",
57
+ ].join("\n")
58
+
52
59
  body = body.join.gsub("</body>", "#{hot_reload_script}\n</body>")
53
60
 
54
61
  [body]
55
62
  end
56
63
 
64
+ def params_js
65
+ env = { mode: @mode }
66
+ <<~JS
67
+ (() => {
68
+ window.__SHOPIFY_CLI_ENV__ = #{env.to_json};
69
+ })();
70
+ JS
71
+ end
72
+
57
73
  def create_stream
58
74
  stream = @streams.new
59
75
 
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+
5
+ module ShopifyCLI
6
+ module Theme
7
+ module DevServer
8
+ class Proxy
9
+ class TemplateParamBuilder
10
+ def build
11
+ # Core doesn't support replace_templates
12
+ return {} if core?(current_path)
13
+
14
+ (syncer_templates + request_templates)
15
+ .select { |file| file.liquid? || file.json? }
16
+ .uniq(&:relative_path)
17
+ .map { |file| as_param(file) }
18
+ .to_h
19
+ end
20
+
21
+ def with_core_endpoints(core_endpoints)
22
+ @core_endpoints = core_endpoints
23
+ self
24
+ end
25
+
26
+ def with_syncer(syncer)
27
+ @syncer = syncer
28
+ self
29
+ end
30
+
31
+ def with_rack_env(rack_env)
32
+ @rack_env = rack_env
33
+ self
34
+ end
35
+
36
+ def with_theme(theme)
37
+ @theme = theme
38
+ self
39
+ end
40
+
41
+ private
42
+
43
+ def as_param(file)
44
+ ["replace_templates[#{file.relative_path}]", file.read]
45
+ end
46
+
47
+ def syncer_templates
48
+ @syncer&.pending_updates || []
49
+ end
50
+
51
+ def request_templates
52
+ cookie_sections
53
+ .map { |section| @theme[section] unless @theme.nil? }
54
+ .compact
55
+ end
56
+
57
+ def cookie_sections
58
+ CGI::Cookie.parse(cookie)["hot_reload_sections"].join.split(",") || []
59
+ end
60
+
61
+ def core?(path)
62
+ core_endpoints.include?(path)
63
+ end
64
+
65
+ def current_path
66
+ rack_env["PATH_INFO"]
67
+ end
68
+
69
+ def cookie
70
+ rack_env["HTTP_COOKIE"]
71
+ end
72
+
73
+ def core_endpoints
74
+ @core_endpoints || []
75
+ end
76
+
77
+ def rack_env
78
+ @rack_env || {}
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -2,6 +2,9 @@
2
2
  require "net/http"
3
3
  require "stringio"
4
4
  require "time"
5
+ require "cgi"
6
+
7
+ require_relative "proxy/template_param_builder"
5
8
 
6
9
  module ShopifyCLI
7
10
  module Theme
@@ -15,6 +18,7 @@ module ShopifyCLI
15
18
  "trailer",
16
19
  "transfer-encoding",
17
20
  "upgrade",
21
+ "content-security-policy",
18
22
  ]
19
23
 
20
24
  class Proxy
@@ -112,21 +116,12 @@ module ShopifyCLI
112
116
  end
113
117
 
114
118
  def build_replace_templates_param(env)
115
- params = {}
116
-
117
- # Core doesn't support replace_templates
118
- return params if @core_endpoints.include?(env["PATH_INFO"])
119
-
120
- pending_templates = @syncer.pending_updates.select do |file|
121
- # Only replace Liquid or JSON files
122
- file.liquid? || file.json?
123
- end
124
-
125
- pending_templates.each do |path|
126
- params["replace_templates[#{path.relative_path}]"] = path.read
127
- end
128
-
129
- params
119
+ TemplateParamBuilder.new
120
+ .with_core_endpoints(@core_endpoints)
121
+ .with_syncer(@syncer)
122
+ .with_theme(@theme)
123
+ .with_rack_env(env)
124
+ .build
130
125
  end
131
126
 
132
127
  def add_session_cookie(cookie_header)
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ module DevServer
6
+ class ReloadMode
7
+ MODES = [:"hot-reload", :"full-page", :off]
8
+
9
+ class << self
10
+ def default
11
+ :"hot-reload"
12
+ end
13
+
14
+ def get!(mode)
15
+ MODES.find { |m| m == mode.to_sym } || raise_error(mode)
16
+ end
17
+
18
+ private
19
+
20
+ def raise_error(invalid_mode)
21
+ error_message = ShopifyCLI::Context.message("theme.serve.reload_mode_is_not_valid", invalid_mode)
22
+ help_message = ShopifyCLI::Context.message("theme.serve.try_a_valid_reload_mode", valid_modes)
23
+
24
+ ShopifyCLI::Context.abort(error_message, help_message)
25
+ end
26
+
27
+ def valid_modes
28
+ MODES.map { |v| "`#{v}`" }.join(", ")
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end