shopify-cli 2.7.3 → 2.7.4

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +15 -0
  4. data/Gemfile.lock +1 -1
  5. data/dev.yml +2 -2
  6. data/ext/javy/javy.rb +7 -8
  7. data/lib/graphql/get_extension_registrations.graphql +27 -0
  8. data/lib/project_types/extension/cli.rb +27 -2
  9. data/lib/project_types/extension/commands/build.rb +10 -10
  10. data/lib/project_types/extension/commands/create.rb +2 -3
  11. data/lib/project_types/extension/commands/push.rb +36 -8
  12. data/lib/project_types/extension/extension_project.rb +1 -1
  13. data/lib/project_types/extension/features/argo_serve.rb +6 -5
  14. data/lib/project_types/extension/forms/questions/ask_registration.rb +6 -2
  15. data/lib/project_types/extension/loaders/project.rb +29 -0
  16. data/lib/project_types/extension/loaders/specification_handler.rb +22 -0
  17. data/lib/project_types/extension/messages/messages.rb +4 -0
  18. data/lib/project_types/extension/models/app.rb +1 -1
  19. data/lib/project_types/extension/models/specification_handlers/default.rb +4 -0
  20. data/lib/project_types/extension/tasks/convert_server_config.rb +3 -1
  21. data/lib/project_types/extension/tasks/execute_commands/base.rb +13 -0
  22. data/lib/project_types/extension/tasks/execute_commands/build.rb +29 -0
  23. data/lib/project_types/extension/tasks/execute_commands/create.rb +33 -0
  24. data/lib/project_types/extension/tasks/execute_commands/serve.rb +35 -0
  25. data/lib/project_types/extension/tasks/merge_server_config.rb +33 -22
  26. data/lib/project_types/script/cli.rb +2 -0
  27. data/lib/project_types/script/commands/connect.rb +19 -0
  28. data/lib/project_types/script/layers/application/connect_app.rb +9 -3
  29. data/lib/project_types/script/layers/application/create_script.rb +16 -16
  30. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +21 -0
  31. data/lib/project_types/script/messages/messages.rb +12 -1
  32. data/lib/project_types/theme/commands/push.rb +3 -1
  33. data/lib/project_types/theme/messages/messages.rb +1 -0
  34. data/lib/shopify_cli/command.rb +6 -0
  35. data/lib/shopify_cli/constants.rb +4 -0
  36. data/lib/shopify_cli/context.rb +1 -1
  37. data/lib/shopify_cli/form.rb +2 -0
  38. data/lib/shopify_cli/messages/messages.rb +7 -1
  39. data/lib/shopify_cli/partners_api/app_extensions/job.rb +36 -0
  40. data/lib/shopify_cli/partners_api/app_extensions.rb +46 -0
  41. data/lib/shopify_cli/partners_api/organizations.rb +2 -5
  42. data/lib/shopify_cli/partners_api.rb +1 -0
  43. data/lib/shopify_cli/project.rb +8 -7
  44. data/lib/shopify_cli/resources/env_file.rb +13 -5
  45. data/lib/shopify_cli/theme/dev_server/hot-reload.js +15 -2
  46. data/lib/shopify_cli/theme/dev_server/proxy/template_param_builder.rb +84 -0
  47. data/lib/shopify_cli/theme/dev_server/proxy.rb +9 -15
  48. data/lib/shopify_cli/thread_pool/job.rb +27 -0
  49. data/lib/shopify_cli/thread_pool.rb +37 -0
  50. data/lib/shopify_cli/version.rb +1 -1
  51. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +3 -1
  52. metadata +15 -4
  53. data/lib/graphql/all_orgs_with_extensions.graphql +0 -37
  54. data/lib/project_types/extension/tasks/run_extension_command.rb +0 -82
@@ -13,6 +13,7 @@ module Script
13
13
  hidden_feature(feature_set: :script_project)
14
14
  subcommand :Create, "create", Project.project_filepath("commands/create")
15
15
  subcommand :Push, "push", Project.project_filepath("commands/push")
16
+ subcommand :Connect, "connect", Project.project_filepath("commands/connect")
16
17
  subcommand :Javy, "javy", Project.project_filepath("commands/javy")
17
18
  end
18
19
  ShopifyCLI::Commands.register("Script::Command", "script")
@@ -24,6 +25,7 @@ module Script
24
25
  autoload :AskScriptUuid, Project.project_filepath("forms/ask_script_uuid")
25
26
  autoload :RunAgainstShopifyOrg, Project.project_filepath("forms/run_against_shopify_org")
26
27
  autoload :Create, Project.project_filepath("forms/create")
28
+ autoload :Connect, Project.project_filepath("forms/connect")
27
29
  autoload :ScriptForm, Project.project_filepath("forms/script_form")
28
30
  end
29
31
 
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ module Script
3
+ class Command
4
+ class Connect < ShopifyCLI::Command::SubCommand
5
+ prerequisite_task :ensure_authenticated
6
+ prerequisite_task ensure_project_type: :script
7
+
8
+ def call(_args, _)
9
+ Layers::Application::ConnectApp.call(ctx: @ctx, force: true, strict: true)
10
+ rescue StandardError => e
11
+ UI::ErrorHandler.pretty_print_and_raise(e, failed_op: @ctx.message("script.connect.error.operation_failed"))
12
+ end
13
+
14
+ def self.help
15
+ ShopifyCLI::Context.new.message("connect.help", ShopifyCLI::TOOL_NAME, ShopifyCLI::TOOL_NAME)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -7,10 +7,11 @@ module Script
7
7
  module Application
8
8
  class ConnectApp
9
9
  class << self
10
- def call(ctx:)
10
+ def call(ctx:, force: false, strict: false)
11
11
  script_project_repo = Layers::Infrastructure::ScriptProjectRepository.new(ctx: ctx)
12
12
  script_project = script_project_repo.get
13
- return false if script_project.env_valid?
13
+
14
+ return false if script_project.env_valid? && !force
14
15
 
15
16
  if ShopifyCLI::Shopifolk.check && Forms::RunAgainstShopifyOrg.ask(ctx, nil, nil).response
16
17
  ShopifyCLI::Shopifolk.act_as_shopify_organization
@@ -37,13 +38,18 @@ module Script
37
38
  extension_point_type = script_project.extension_point_type
38
39
  scripts = script_service.get_app_scripts(extension_point_type: extension_point_type)
39
40
 
40
- uuid = Forms::AskScriptUuid.ask(ctx, scripts, nil).uuid
41
+ uuid = Forms::AskScriptUuid.ask(ctx, scripts, nil)&.uuid
42
+
43
+ if strict && uuid.nil?
44
+ ctx.abort(ctx.message("script.connect.missing_script"))
45
+ end
41
46
 
42
47
  script_project_repo.create_env(
43
48
  api_key: app["apiKey"],
44
49
  secret: app["apiSecretKeys"].first["secret"],
45
50
  uuid: uuid
46
51
  )
52
+ ctx.done(ctx.message("script.connect.connected", app["title"]))
47
53
 
48
54
  true
49
55
  end
@@ -8,11 +8,14 @@ module Script
8
8
  class CreateScript
9
9
  class << self
10
10
  def call(ctx:, language:, sparse_checkout_branch:, script_name:, extension_point_type:)
11
- raise Infrastructure::Errors::ScriptProjectAlreadyExistsError, script_name if ctx.dir_exist?(script_name)
11
+ script_project_repo = Infrastructure::ScriptProjectRepository.new(
12
+ ctx: ctx,
13
+ directory: script_name,
14
+ initial_directory: ctx.root
15
+ )
12
16
 
13
- in_new_directory_context(ctx, script_name) do
17
+ in_new_directory_context(script_project_repo) do
14
18
  extension_point = ExtensionPoints.get(type: extension_point_type)
15
- script_project_repo = Infrastructure::ScriptProjectRepository.new(ctx: ctx)
16
19
  project = script_project_repo.create(
17
20
  script_name: script_name,
18
21
  extension_point_type: extension_point_type,
@@ -62,19 +65,16 @@ module Script
62
65
  ProjectDependencies.install(ctx: ctx, task_runner: task_runner)
63
66
  end
64
67
 
65
- def in_new_directory_context(ctx, directory)
66
- initial_directory = ctx.root
67
- begin
68
- ctx.mkdir_p(directory)
69
- ctx.chdir(directory)
70
- yield
71
- rescue
72
- ctx.chdir(initial_directory)
73
- ctx.rm_r(directory)
74
- raise
75
- ensure
76
- ctx.chdir(initial_directory)
77
- end
68
+ def in_new_directory_context(script_project_repo)
69
+ script_project_repo.create_project_directory
70
+ yield
71
+ rescue Infrastructure::Errors::ScriptProjectAlreadyExistsError
72
+ raise
73
+ rescue
74
+ script_project_repo.delete_project_directory
75
+ raise
76
+ ensure
77
+ script_project_repo.change_to_initial_directory
78
78
  end
79
79
  end
80
80
  end
@@ -6,9 +6,26 @@ module Script
6
6
  class ScriptProjectRepository
7
7
  include SmartProperties
8
8
  property! :ctx, accepts: ShopifyCLI::Context
9
+ property :directory, accepts: String
10
+ property :initial_directory, accepts: String
9
11
 
10
12
  MUTABLE_ENV_VALUES = %i(uuid)
11
13
 
14
+ def create_project_directory
15
+ raise Infrastructure::Errors::ScriptProjectAlreadyExistsError, directory if ctx.dir_exist?(directory)
16
+ ctx.mkdir_p(directory)
17
+ change_directory(directory: directory)
18
+ end
19
+
20
+ def delete_project_directory
21
+ change_to_initial_directory
22
+ ctx.rm_r(directory)
23
+ end
24
+
25
+ def change_to_initial_directory
26
+ change_directory(directory: initial_directory)
27
+ end
28
+
12
29
  def create(script_name:, extension_point_type:, language:)
13
30
  validate_metadata!(extension_point_type, language)
14
31
 
@@ -95,6 +112,10 @@ module Script
95
112
 
96
113
  private
97
114
 
115
+ def change_directory(directory:)
116
+ ctx.chdir(directory)
117
+ end
118
+
98
119
  def capture_io(&block)
99
120
  CLI::UI::StdoutRouter::Capture.new(&block).run
100
121
  end
@@ -144,6 +144,7 @@ module Script
144
144
 
145
145
  language_library_for_api_not_found_cause: "Script can’t be pushed because the %{language} library for API %{api} is missing.",
146
146
  language_library_for_api_not_found_help: "Make sure extension_point.yml contains the correct API library.",
147
+ no_scripts_found_in_app: "The selected apps have no scripts. Please, create them first on the partners' dashboard.",
147
148
  },
148
149
 
149
150
  create: {
@@ -179,7 +180,17 @@ module Script
179
180
 
180
181
  script_pushed: "{{v}} Script pushed to app (API key: %{api_key}).",
181
182
  },
182
-
183
+ connect: {
184
+ connected: "Connected! Your project is now connected to {{green:%s}}",
185
+ missing_script: "No script has been selected.",
186
+ help: <<~HELP,
187
+ {{command:%s script connect}}: Connects an existing script to an app.
188
+ Usage: {{command:%s script connect}}
189
+ HELP
190
+ error: {
191
+ operation_failed: "Couldn't connect script to app.",
192
+ },
193
+ },
183
194
  javy: {
184
195
  help: <<~HELP,
185
196
  Compile the JavaScript code into WebAssembly.
@@ -51,7 +51,9 @@ module Theme
51
51
  end
52
52
 
53
53
  if theme.live? && !options.flags[:allow_live]
54
- return unless CLI::UI::Prompt.confirm(@ctx.message("theme.push.live"))
54
+ question = @ctx.message("theme.push.live")
55
+ question += @ctx.message("theme.push.theme", theme.name, theme.id) if options.flags[:live]
56
+ return unless CLI::UI::Prompt.confirm(question)
55
57
  end
56
58
 
57
59
  ignore_filter = ShopifyCLI::Theme::IgnoreFilter.from_path(root)
@@ -74,6 +74,7 @@ module Theme
74
74
  push: "Pushing theme files to Shopify",
75
75
  select: "Select theme to push to",
76
76
  live: "Are you sure you want to push to your live theme?",
77
+ theme: "\n Theme: {{blue:%s #%s}} {{green:[live]}}",
77
78
  theme_not_found: "Theme #%s doesn't exist",
78
79
  done: <<~DONE,
79
80
  {{green:Your theme was pushed successfully}}
@@ -29,6 +29,12 @@ module ShopifyCLI
29
29
  run_prerequisites
30
30
  cmd.call(args, command_name)
31
31
  end
32
+ rescue OptionParser::InvalidOption => error
33
+ arg = error.args.first
34
+ raise ShopifyCLI::Abort, @ctx.message("core.errors.option_parser.invalid_option", arg)
35
+ rescue OptionParser::MissingArgument => error
36
+ arg = error.args.first
37
+ raise ShopifyCLI::Abort, @ctx.message("core.errors.option_parser.missing_argument", arg)
32
38
  end
33
39
 
34
40
  def options(&block)
@@ -61,5 +61,9 @@ module ShopifyCLI
61
61
  module Links
62
62
  NEW_ISSUE = "https://github.com/Shopify/shopify-cli/issues/new"
63
63
  end
64
+
65
+ module Extension
66
+ DEFAULT_PORT = 39351
67
+ end
64
68
  end
65
69
  end
@@ -90,7 +90,7 @@ module ShopifyCLI
90
90
 
91
91
  # will return true if being launched from a tty
92
92
  def tty?
93
- $stdin.tty? && !testing?
93
+ !testing? && $stdin.tty?
94
94
  end
95
95
 
96
96
  # will return true if the cli is being run from an installation, and not a
@@ -15,6 +15,8 @@ module ShopifyCLI
15
15
  rescue ShopifyCLI::Abort => err
16
16
  ctx.puts(err.message)
17
17
  nil
18
+ rescue ShopifyCLI::AbortSilent
19
+ nil
18
20
  end
19
21
  end
20
22
 
@@ -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.
@@ -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",
@@ -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,11 +14,15 @@ module ShopifyCLI
14
14
  }
15
15
 
16
16
  class << self
17
- def read(_directory = Dir.pwd)
18
- input = parse_external_env
17
+ def read(_directory = Dir.pwd, overrides: {})
18
+ input = parse_external_env(overrides: overrides)
19
19
  new(input)
20
20
  end
21
21
 
22
+ def from_hash(hash)
23
+ new(env_input(hash))
24
+ end
25
+
22
26
  def parse(directory)
23
27
  File.read(File.join(directory, FILENAME))
24
28
  .gsub("\r\n", "\n").split("\n").each_with_object({}) do |line, output|
@@ -37,10 +41,14 @@ module ShopifyCLI
37
41
  end
38
42
  end
39
43
 
40
- def parse_external_env(directory = Dir.pwd)
44
+ def parse_external_env(directory = Dir.pwd, overrides: {})
45
+ env_input(parse(directory), overrides: overrides)
46
+ end
47
+
48
+ def env_input(parsed_source, overrides: {})
41
49
  env_details = {}
42
50
  extra = {}
43
- parse(directory).each do |key, value|
51
+ parsed_source.merge(overrides).each do |key, value|
44
52
  if KEY_MAP[key]
45
53
  env_details[KEY_MAP[key]] = value
46
54
  else
@@ -53,7 +61,7 @@ module ShopifyCLI
53
61
  end
54
62
 
55
63
  property :api_key, required: true
56
- property :secret, required: true
64
+ property :secret
57
65
  property :shop
58
66
  property :scopes
59
67
  property :host
@@ -33,7 +33,20 @@
33
33
  }
34
34
  }
35
35
 
36
- function refreshPage() {
36
+ function setHotReloadCookie(files) {
37
+ var date = new Date();
38
+
39
+ // Hot reload cookie expires in 3 seconds
40
+ date.setSeconds(date.getSeconds() + 3);
41
+
42
+ var sections = files.join(',');
43
+ var expires = date.toUTCString();
44
+
45
+ document.cookie = `hot_reload_sections=${sections}; expires=${expires}; path=/`;
46
+ }
47
+
48
+ function refreshPage(files) {
49
+ setHotReloadCookie(files);
37
50
  console.log('[HotReload] Refreshing entire page');
38
51
  window.location.reload();
39
52
  }
@@ -43,7 +56,7 @@
43
56
  var modifiedFiles = data.modified;
44
57
 
45
58
  if (isRefreshRequired(modifiedFiles)) {
46
- refreshPage();
59
+ refreshPage(modifiedFiles);
47
60
  } else {
48
61
  modifiedFiles.forEach(refreshFile);
49
62
  }
@@ -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
@@ -112,21 +115,12 @@ module ShopifyCLI
112
115
  end
113
116
 
114
117
  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
118
+ TemplateParamBuilder.new
119
+ .with_core_endpoints(@core_endpoints)
120
+ .with_syncer(@syncer)
121
+ .with_theme(@theme)
122
+ .with_rack_env(env)
123
+ .build
130
124
  end
131
125
 
132
126
  def add_session_cookie(cookie_header)
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ class ThreadPool
5
+ class Job
6
+ attr_reader :error
7
+
8
+ def perform!
9
+ raise "`#{self.class.name}#perform!` must be defined"
10
+ end
11
+
12
+ def call
13
+ perform!
14
+ rescue StandardError => error
15
+ @error = error
16
+ end
17
+
18
+ def success?
19
+ !@error
20
+ end
21
+
22
+ def error?
23
+ !!@error
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ class ThreadPool
5
+ attr_reader :errors
6
+
7
+ def initialize(pool_size: 10)
8
+ @jobs = Queue.new
9
+ @pool = Array.new(pool_size) { spawn_thread }
10
+ end
11
+
12
+ def schedule(job)
13
+ @jobs << job
14
+ end
15
+
16
+ def shutdown
17
+ @pool.size.times do
18
+ schedule(-> { throw(:stop_thread) })
19
+ end
20
+ @pool.map(&:join)
21
+ ensure
22
+ @jobs.close
23
+ end
24
+
25
+ private
26
+
27
+ def spawn_thread
28
+ Thread.new do
29
+ catch(:stop_thread) do
30
+ loop do
31
+ @jobs.pop.call
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end