shopify-cli 2.7.3 → 2.7.4

Sign up to get free protection for your applications and to get access to all the features.
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