svelte-on-rails 22.4.5 → 22.5.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c20418140ddcc6c13f764825f0fb67fc0753d5cf111b6ca4dd536c791b8aa15a
4
- data.tar.gz: ffcee0dc8b10538a673c3c1e2992f11b2a1c3b03de2d06d761ddd7ebd343c3b9
3
+ metadata.gz: 1bcf2e7619271bc0d415deb797d15d3d7625640171fa02485fedded0bb526ac6
4
+ data.tar.gz: b7c3acb70f336fb7c9ff4b203474ed584c9ba280e7a080b959d50dc73e82e9e4
5
5
  SHA512:
6
- metadata.gz: 6f3b8e485b02bc666cb9dcb6876941ab4c0781c9ab834e1fb5390a0327c83ba201f8d3e4219c4a0edd230e608f50d09b6508cd43dff25958f645ca402b489af9
7
- data.tar.gz: b713bfea90974924c29f92374daa4196f7c15a70e9baf47fe7593048ac2af4f8c8b1b8917913de975bf80cc1a4f00c111fe03fbe3dc696fc9be9295dad6a2dab
6
+ metadata.gz: 4df7c21e84b8f1805b0e76fc11dccd5a890fc589399f7945763974f53038b6b595e3a5e834be13857ad0f1da0e20f718df0400cf3965ece7849265fe000c6e80
7
+ data.tar.gz: a7ad01afc86a956759a79d710806d162f10d7dc400fb0ab2801dd01ae5335d52976c292fb15a2c95bc18fd85dcdd0dd88ad158533c7228572f2121b479e0379d
@@ -2,97 +2,64 @@ module SvelteOnRails
2
2
  module BootContext
3
3
  module_function
4
4
 
5
- # We deliberately use a BLACKLIST + program-name check, not a whitelist of
6
- # loaded constants. Reason: `puma` is typically in the app's Gemfile default
7
- # group, so `defined?(::Puma::Launcher)` is truthy even inside `rails console`
8
- # or `rails assets:precompile`.
5
+ # Returns true when we are certain the current process is a Puma web
6
+ # server (or a `rails server` process that will boot Puma). Everywhere
7
+ # else (rake, console, runner, tests, generators, unknown wrappers)
8
+ # returns false. False negatives are safe because the SsrClient has a
9
+ # lazy-start fallback that kicks in on the first render request.
9
10
  def serving_http?
10
- # Explicit overrides for tests / edge cases:
11
- return true if ENV["SVELTE_ON_RAILS_FORCE_SSR"] == "1"
11
+ # 1. Explicit user overrides always win.
12
+ return true if ENV["SVELTE_ON_RAILS_FORCE_SSR"] == "1"
12
13
  return false if ENV["SVELTE_ON_RAILS_DISABLE_SSR"] == "1"
13
14
 
14
- return false if rake_task?
15
- return false if rails_console?
16
- return false if rails_runner?
17
- return false if rails_dbconsole?
18
- return false if rails_generator?
19
- return false if test_runner?
20
-
21
- # Positive signal: `rails server` sets this constant during command dispatch.
22
- return true if defined?(Rails::Server)
23
-
24
- # Positive signal: launched directly via a server binary (bundle exec puma,
25
- # rackup, unicorn, falcon, passenger). $PROGRAM_NAME is set by the launcher
26
- # before it requires the app, so this is reliable.
27
- return true if server_program_name?
28
-
29
- # Default: assume this is NOT an HTTP-serving context. Better to be silent
30
- # than to spawn an unwanted Node process; users can force with the ENV var.
15
+ # 2. Positive signal: `rails server` has been dispatched. This
16
+ # constant is defined by Rails::Command::ServerCommand before the
17
+ # app boots, and is a very reliable indicator.
18
+ return true if defined?(::Rails::Server)
19
+
20
+ # 3. Positive signal: a Puma::Launcher instance is already alive.
21
+ # Puma instantiates its Launcher inside Puma::Runner#run, which
22
+ # runs BEFORE Rails' after_initialize (because Puma requires the
23
+ # Rack app, and requiring the app is what triggers Rails boot).
24
+ # So by the time this method is called from our after_initialize
25
+ # hook, the launcher exists in ObjectSpace iff we are inside Puma.
26
+ return true if puma_launcher_alive?
27
+
28
+ # 4. Positive signal: launched via `puma` / `pumactl` binary directly.
29
+ # Redundant with (3) in most cases, but harmless and useful for
30
+ # exotic launchers that bypass Puma::Runner.
31
+ return true if program_basename == "puma"
32
+
33
+ # Everything else: not an HTTP-serving context (as far as we can tell
34
+ # at boot time). If we're wrong, the lazy fallback will handle it.
31
35
  false
32
36
  end
33
37
 
38
+ # Short human-readable label for the log line when we skip SSR startup.
34
39
  def description
35
- return "rake tasks: #{Rake.application.top_level_tasks.join(', ')}" if rake_task?
36
- return "rails console" if rails_console?
37
- return "rails runner" if rails_runner?
38
- return "rails dbconsole" if rails_dbconsole?
39
- return "rails generator" if rails_generator?
40
- return "test runner (#{test_runner_name})" if test_runner?
41
- return "rails server" if defined?(Rails::Server)
42
- "program=#{File.basename($PROGRAM_NAME)}"
43
- end
44
-
45
- # --- detectors -----------------------------------------------------------
46
-
47
- def rake_task?
48
- defined?(Rake) && Rake.respond_to?(:application) && Rake.application.top_level_tasks.any?
49
- end
50
-
51
- def rails_console?
52
- defined?(Rails::Console) ||
53
- program_basename == "irb" ||
54
- (defined?(IRB) && $PROGRAM_NAME.to_s.include?("irb"))
55
- end
56
-
57
- def rails_runner?
58
- defined?(Rails::Command::RunnerCommand) &&
59
- Rails::Command.const_defined?(:RunnerCommand) &&
60
- current_rails_command?("runner")
40
+ return "rails server" if defined?(::Rails::Server)
41
+ return "puma" if puma_launcher_alive? || program_basename == "puma"
42
+ return "rake: #{Rake.application.top_level_tasks.join(', ')}" if rake_task?
43
+ return "rails console" if defined?(::Rails::Console)
44
+ return "rails runner" if defined?(::Rails::Command::RunnerCommand)
45
+ return "rails dbconsole" if defined?(::Rails::DBConsole)
46
+ "program=#{program_basename}"
61
47
  end
62
48
 
63
- def rails_dbconsole?
64
- defined?(Rails::DBConsole) || current_rails_command?("dbconsole")
65
- end
66
-
67
- def rails_generator?
68
- defined?(Rails::Generators) && current_rails_command?("generate", "destroy", "new")
69
- end
70
-
71
- def test_runner?
72
- !test_runner_name.nil?
73
- end
74
-
75
- def test_runner_name
76
- return "rspec" if $PROGRAM_NAME.to_s.include?("rspec")
77
- return "minitest" if defined?(Minitest) && Minitest.respond_to?(:run) && !defined?(Rails::Server)
78
- return "cucumber" if $PROGRAM_NAME.to_s.include?("cucumber")
79
- nil
49
+ def puma_launcher_alive?
50
+ return false unless defined?(::Puma::Launcher)
51
+ ObjectSpace.each_object(::Puma::Launcher).any?
52
+ rescue
53
+ false
80
54
  end
81
55
 
82
- def server_program_name?
83
- %w[puma rackup unicorn unicorn_rails falcon passenger].include?(program_basename)
56
+ def rake_task?
57
+ defined?(::Rake) && Rake.respond_to?(:application) &&
58
+ Rake.application.top_level_tasks.any?
84
59
  end
85
60
 
86
61
  def program_basename
87
62
  File.basename($PROGRAM_NAME.to_s)
88
63
  end
89
-
90
- # `rails <command>` sets ARGV[0] to the command name before Rails::Command
91
- # dispatches. This is stable across Rails 6/7/8.
92
- def current_rails_command?(*names)
93
- argv0 = (defined?(ARGV) && ARGV.first) || nil
94
- return false unless argv0
95
- names.include?(argv0.to_s)
96
- end
97
64
  end
98
65
  end
@@ -240,7 +240,7 @@ module SvelteOnRails
240
240
  def render_with_ssr_server(caller_loc)
241
241
  return {} unless @conf.configs[:ssr_server]
242
242
 
243
- result = SvelteOnRails::SsrClient.instance.render(
243
+ result = SvelteOnRails::SsrClient.instance(caller: "Request/View-helper").render(
244
244
  component_paths,
245
245
  cached_props,
246
246
  debug?,
@@ -23,7 +23,7 @@ module SvelteOnRails
23
23
  )
24
24
  $stdout.flush
25
25
  elsif SvelteOnRails::Configuration.instance.configs[:ssr]
26
- SvelteOnRails::SsrClient.instance
26
+ SvelteOnRails::SsrClient.instance(caller: 'initializer/booting')
27
27
  end
28
28
 
29
29
  end
@@ -52,8 +52,9 @@ module SvelteOnRails
52
52
  end
53
53
 
54
54
  rake_tasks do
55
- load File.expand_path("../../tasks/svelte_on_rails_tasks.rake", __FILE__)
55
+ load File.expand_path("../../tasks/general_tasks.rake", __FILE__)
56
56
  load File.expand_path("../../tasks/cache_tasks.rake", __FILE__)
57
+ load File.expand_path("../../tasks/ssr_client_tasks.rake", __FILE__)
57
58
  end
58
59
  end
59
60
  end
@@ -10,34 +10,56 @@ module SvelteOnRails
10
10
  :logfile_max_lines,
11
11
  :socket_path,
12
12
  :ssr_server_bin_path,
13
- :node_bin
13
+ :node_bin,
14
+ :initialized_by,
15
+ :render_success_count,
16
+ :render_failure_count,
17
+ :error_log
14
18
 
15
19
  # methods
16
20
 
17
- def self.instance
18
- @instance ||= new
21
+ def self.instance(caller: nil)
22
+
23
+ @instance ||= new(caller: caller)
24
+ @instance
19
25
  end
20
26
 
21
- def initialize
27
+ def initialize(caller: nil)
22
28
 
23
- SvelteOnRails::Lib::Utils.ssr_server_logfile.delete if SvelteOnRails::Lib::Utils.ssr_server_logfile.exist?
29
+ @initialized_by = caller
24
30
 
25
- @helpers = SsrServerHelpers.new
31
+ File.open(SvelteOnRails::Lib::Utils.ssr_server_logfile, 'w') do |f|
32
+ f.sync = true
33
+ f.puts("[#{Time.now.utc.iso8601(3)} ppid:#{Process.ppid} pid:#{Process.pid}] >>> Logfile deleted on initializing SsrClient, called by #{caller} <<<")
34
+ end
35
+
36
+ @socket_namespace = [
37
+ "svelte",
38
+ Rails.application.class.module_parent_name[0..15],
39
+ SsrClientControlChannel.short_env
40
+ ].join("-")
41
+ @socket_path = socket_path_fresh
26
42
 
27
43
  @logfile_path = Rails.root.join("log/svelte-ssr-server-#{Rails.env}.log")
28
44
  @logfile_max_lines = build_logfile_max_lines
29
- @socket_path = @helpers.socket_path
30
45
  @ssr_server = SvelteOnRails::Configuration.instance.configs[:ssr_server]
31
- @ssr_server_bin_path = @helpers.ssr_server_bin_path
46
+ @ssr_server_bin_path = SvelteOnRails::Configuration.instance.ssr_server_bin_path
47
+ @started = Time.now
48
+
49
+ @render_success_count = 0
50
+ @render_failure_count = 0
51
+ @error_log = []
32
52
 
33
53
  File.delete(restart_marker_file) if File.exist?(restart_marker_file)
34
54
 
55
+ SvelteOnRails::SsrClientControlChannel.install_control_listener
56
+
35
57
  if @ssr_server
36
58
 
37
59
  start_node_server!
38
60
 
39
61
  at_exit do
40
- @helpers.shutdown(caller: 'at_exit')
62
+ shutdown(caller: 'at_exit')
41
63
  File.delete(restart_marker_file) if File.exist?(restart_marker_file)
42
64
  end
43
65
  else
@@ -69,6 +91,9 @@ module SvelteOnRails
69
91
  socket.close
70
92
  rescue => e
71
93
 
94
+ write_error_log(e)
95
+ @render_failure_count += 1
96
+
72
97
  restart_my_server('Restart-after-failure')
73
98
 
74
99
  SvelteOnRails::Lib::Utils.error_log(
@@ -88,6 +113,7 @@ module SvelteOnRails
88
113
 
89
114
  raw_response = parse_body_json(response)
90
115
  if raw_response['status'] == 'SUCCESS'
116
+ @render_success_count += 1
91
117
  htm = raw_response['html'].to_s
92
118
  .gsub('<!--[-->', '')
93
119
  .gsub('<!--]-->', '')
@@ -108,6 +134,10 @@ module SvelteOnRails
108
134
  else
109
135
  ''
110
136
  end
137
+
138
+ write_error_log("Render #{component[:file_basename]}.svelte")
139
+ @render_failure_count += 1
140
+
111
141
  utils = SvelteOnRails::Lib::Utils
112
142
  utils.error_log(
113
143
  :render_failed,
@@ -124,6 +154,12 @@ module SvelteOnRails
124
154
 
125
155
  end
126
156
 
157
+ def write_error_log(msg)
158
+ timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N')
159
+ @error_log << "[#{timestamp}] #{msg}"
160
+ @error_log.shift while @error_log.size > 50
161
+ end
162
+
127
163
  def parse_body_json(response_string)
128
164
  return {} unless response_string.to_s.present?
129
165
 
@@ -169,21 +205,12 @@ module SvelteOnRails
169
205
 
170
206
  SvelteOnRails::Lib::Utils.ssr_server_log("SOR start_node_server! called", ssr_logfile_only: !restart_after_failure)
171
207
 
172
- # notice ppid
173
- FileUtils.mkdir_p(@helpers.ssr_client_ppid_file.dirname)
174
- File.write(@helpers.ssr_client_ppid_file, Process.ppid.to_s)
175
- if @helpers.ssr_client_ppid_file.exist?
176
- SvelteOnRails::Lib::Utils.ssr_server_log("File #{@helpers.ssr_client_ppid_file} written, PPID: #{Process.ppid}", ssr_logfile_only: true)
177
- else
178
- SvelteOnRails::Lib::Utils.ssr_server_log("ERROR: File #{@helpers.ssr_client_ppid_file} could not be written")
179
- end
180
-
181
208
  start = Time.now
182
209
 
183
210
  @node_server_output = {}
184
211
  Thread.new do
185
212
  stdout, stderr, status = Open3.capture3(
186
- @helpers.start_node_server_command(started_by),
213
+ start_node_server_command(started_by),
187
214
  chdir: Rails.root
188
215
  )
189
216
  @node_server_output = {
@@ -215,7 +242,7 @@ module SvelteOnRails
215
242
  end
216
243
  SvelteOnRails::Lib::Utils.ssr_server_log("Server responded successfully")
217
244
 
218
- @helpers.cleanup_leftover_processes
245
+ cleanup_leftover_processes
219
246
  end
220
247
 
221
248
  def restart_my_server(tag = nil)
@@ -234,7 +261,7 @@ module SvelteOnRails
234
261
  Rails.logger.error(msg)
235
262
  SvelteOnRails::Lib::Utils.ssr_server_log("[ERROR] #{msg}")
236
263
 
237
- @helpers.shutdown(caller: 'restart')
264
+ shutdown(caller: 'restart')
238
265
  begin
239
266
  start_node_server!("#{tag.gsub(' ', '-') + '-' if tag}#{count}", restart_after_failure: true)
240
267
  rescue
@@ -253,6 +280,57 @@ module SvelteOnRails
253
280
  nil
254
281
  end
255
282
 
283
+ def uptime
284
+ Time.now - @started
285
+ end
286
+
287
+ def start_node_server_command(started_by = '')
288
+ workers = SvelteOnRails::Configuration.instance.configs[:ssr_server_workers]
289
+ [
290
+ (started_by ? "SVELTE_SSR_SERVER_STARTED_BY=#{started_by}" : nil),
291
+ SvelteOnRails::Configuration.instance.node_bin_path,
292
+ @ssr_server_bin_path,
293
+ socket_path,
294
+ (workers > 1 ? "--workers=#{workers}" : nil)
295
+ ].compact.join(' ')
296
+ end
297
+
298
+ def shutdown(caller: '')
299
+ SsrClient.instance_variable_set(:@instance, nil)
300
+ SvelteOnRails::Lib::Utils.ssr_server_log("Shutting down SSR client, ppid: #{Process.ppid} (#{caller})")
301
+ terminate_node_processes(own_processes, label: "own")
302
+ {
303
+ success: true
304
+ }
305
+ rescue => e
306
+ {
307
+ error: e
308
+ }
309
+ end
310
+
311
+ # NODE PROCESS MANAGEMENT
312
+
313
+ SsrProcess = Struct.new(:pid, :ppid, :socket_path, keyword_init: true)
314
+
315
+ def own_processes
316
+ scan_processes.select { |p| p.ppid == Process.ppid }
317
+ end
318
+
319
+ def leftover_processes
320
+ scan_processes.reject { |p| p.ppid == Process.ppid }
321
+ end
322
+
323
+ def leftover_sockets
324
+ scan_sockets.reject { |s| s[:ppid] == Process.ppid }
325
+ end
326
+
327
+ def cleanup_leftover_processes
328
+ terminate_node_processes(leftover_processes, label: "leftover")
329
+ delete_sockets(leftover_sockets.map { |s| s[:path] }, label: "leftover")
330
+ end
331
+
332
+ # END node process management
333
+
256
334
  private
257
335
 
258
336
  def restart_marker_file
@@ -325,91 +403,24 @@ module SvelteOnRails
325
403
  end
326
404
  end
327
405
 
328
- end
329
-
330
- class SsrServerHelpers
331
-
332
- # theese helpers are used from the :check rake task
333
- # For that reason they are within a separate class
334
-
335
- attr_reader :socket_namespace,
336
- :socket_path,
337
- :ssr_server_bin_path
338
-
339
- def initialize
340
-
341
- @socket_namespace = [
342
- "svelte-ssr",
343
- Rails.application.class.module_parent_name[0..30],
344
- Rails.env,
345
- ].join("-")
346
-
347
- @ssr_server_bin_path = SvelteOnRails::Configuration.instance.ssr_server_bin_path
348
-
349
- end
350
-
351
- def ssr_client_ppid_file
352
- Rails.root.join("tmp", "pids", 'svelte_on_rails.ppid')
353
- end
354
-
355
- def socket_path(ppid: Process.ppid)
406
+ def socket_path_fresh
407
+ ppid = Process.ppid
356
408
  app_sock = Rails.root.join("tmp/sockets/#{@socket_namespace}-#{ppid}.sock")
357
- FileUtils.mkdir_p(File.dirname(app_sock))
358
-
359
- return app_sock if app_sock.to_s.length < 104
409
+ if app_sock.to_s.length <= 103
410
+ FileUtils.mkdir_p(File.dirname(app_sock))
411
+ return app_sock
412
+ end
360
413
 
361
- # Fallback: socket paths > 104 chars are unreliable on macOS/BSD
414
+ # Fallback: socket paths > 103 chars are unreliable on macOS/BSD
362
415
  FileUtils.rm_f(app_sock) if File.exist?(app_sock)
363
- dir = File.expand_path("/tmp/svelte")
364
- FileUtils.mkdir_p(dir)
365
- FileUtils.chmod(0777, dir)
366
- fallback = Pathname.new("#{dir}/#{@socket_namespace}-#{ppid}.sock")
367
- SvelteOnRails::Lib::Utils.ssr_server_log("sockets longer than 104 chars are risky, fallback to: #{fallback}")
416
+ fallback = Pathname.new("/tmp/#{@socket_namespace}-#{ppid}.sock")
417
+ SvelteOnRails::Lib::Utils.ssr_server_log("sockets longer than 103 chars are risky, fallback to: #{fallback}")
368
418
  fallback
369
419
  end
370
420
 
371
- def start_node_server_command(started_by, ppid: Process.ppid)
372
- workers = SvelteOnRails::Configuration.instance.configs[:ssr_server_workers]
373
- [
374
- (started_by ? "SVELTE_SSR_SERVER_STARTED_BY=#{started_by}" : nil),
375
- SvelteOnRails::Configuration.instance.node_bin_path,
376
- @ssr_server_bin_path,
377
- socket_path(ppid: ppid),
378
- (workers > 1 ? "--workers=#{workers}" : nil)
379
- ].compact.join(' ')
380
- end
421
+ # NODE PROCESS MANAGEMENT
381
422
 
382
- SsrProcess = Struct.new(:pid, :ppid, :socket_path, keyword_init: true)
383
-
384
- def own_processes(current_ppid: Process.ppid)
385
- scan_processes.select { |p| p.ppid == current_ppid }
386
- end
387
-
388
- def leftover_processes(current_ppid: Process.ppid)
389
- scan_processes.reject { |p| p.ppid == current_ppid }
390
- end
391
-
392
- def leftover_sockets(current_ppid: Process.ppid)
393
- scan_sockets.reject { |s| s[:ppid] == current_ppid }
394
- end
395
-
396
- def cleanup_leftover_processes(current_ppid: Process.ppid)
397
- terminate_processes(leftover_processes(current_ppid: current_ppid), label: "leftover")
398
- delete_sockets(leftover_sockets(current_ppid: current_ppid).map { |s| s[:path] }, label: "leftover")
399
- end
400
-
401
- def shutdown(current_ppid: Process.ppid, caller: '')
402
- SvelteOnRails::Lib::Utils.ssr_server_log("Shutting down SSR client, ppid: #{current_ppid} (#{caller})")
403
- terminate_processes(own_processes(current_ppid: current_ppid), label: "own")
404
- ensure
405
- File.delete(ssr_client_ppid_file) if File.exist?(ssr_client_ppid_file)
406
- end
407
-
408
- private
409
-
410
- # Cluster mode: a primary + N workers share one socket path.
411
- # SIGINT the primary first (lowest pid per socket group); survivors get SIGINT too.
412
- def terminate_processes(processes, label:)
423
+ def terminate_node_processes(processes, label:)
413
424
  return if processes.empty?
414
425
 
415
426
  processes.group_by(&:socket_path).values.flat_map { |g| g.sort_by(&:pid) }.each do |p|
@@ -432,7 +443,6 @@ module SvelteOnRails
432
443
  SvelteOnRails::Lib::Utils.ssr_server_log("Deleted #{socket_paths.size} #{label} socket#{'s' if socket_paths.size != 1}")
433
444
  end
434
445
 
435
- # All running SSR processes matching our namespace (any ppid).
436
446
  def scan_processes
437
447
  stdout, = Open3.capture3("ps aux | grep svelte | grep -v grep")
438
448
  regex = %r{(/\S*#{Regexp.escape(@socket_namespace)}-(\d+)\.sock)\b}
@@ -444,16 +454,115 @@ module SvelteOnRails
444
454
  end
445
455
  end
446
456
 
447
- # All socket files on disk matching our namespace (any ppid).
448
- # Looks in both the app's tmp/sockets dir and the macOS/BSD fallback dir.
449
457
  def scan_sockets
450
- dirs = [Rails.root.join("tmp/sockets").to_s, "/tmp/svelte"]
458
+ dirs = [Rails.root.join("tmp/sockets").to_s, "/tmp"]
451
459
  regex = /#{Regexp.escape(@socket_namespace)}-(\d+)\.sock\z/
452
460
 
453
461
  dirs.flat_map { |d| Dir.glob(File.join(d, "#{@socket_namespace}-*.sock")) }
454
462
  .filter_map { |path| (m = path.match(regex)) && { path: path, ppid: m[1].to_i } }
455
463
  end
456
464
 
465
+ # END node process management
466
+
467
+ end
468
+
469
+ class SsrClientControlChannel
470
+
471
+ # Full path of the control socket.
472
+ # Sits next to the SSR socket in tmp/sockets, or in the /tmp/svelte
473
+ # fallback directory when Rails.root pushes total length over 103 chars.
474
+ def self.socket_path
475
+ primary = Rails.root.join("tmp", "sockets", "svelte-ctl-#{short_env}.sock")
476
+ if primary.to_s.length <= 103
477
+ FileUtils.mkdir_p(File.dirname(primary))
478
+ return primary
479
+ end
480
+
481
+ Pathname.new("/tmp/svelte-#{short_env}-ctl-#{Digest::XXH64.hexdigest(Rails.root.to_s)}.sock")
482
+ end
483
+
484
+ # enable communication by rake task for admin purposes
485
+ def self.install_control_listener
486
+
487
+ path = socket_path.to_s
488
+ FileUtils.mkdir_p(File.dirname(path))
489
+ File.unlink(path) if File.exist?(path)
490
+
491
+ server = UNIXServer.new(path)
492
+ File.chmod(0600, path)
493
+
494
+ Thread.new do
495
+ loop do
496
+ client = server.accept
497
+ begin
498
+ request = client.gets&.strip.to_sym
499
+ client_instance = SsrClient.instance(caller: 'rake task (manual)')
500
+
501
+ response = case request
502
+ when :status
503
+ {
504
+ env: Rails.env,
505
+ uptime: client_instance.uptime,
506
+ request: request,
507
+ socket_path: client_instance.socket_path,
508
+ initialized_by: client_instance.initialized_by,
509
+ ping: client_instance.ping?,
510
+ start_node_server_command: client_instance.start_node_server_command,
511
+ render_success_count: client_instance.render_success_count,
512
+ render_failure_count: client_instance.render_failure_count,
513
+ error_log: client_instance.error_log
514
+ }
515
+ when :stop
516
+ begin
517
+ client_instance.shutdown
518
+ {
519
+ success: true
520
+ }
521
+ rescue => e
522
+ {
523
+ error: "Failed to stop SSR server => #{e}"
524
+ }
525
+ end
526
+ end
527
+
528
+ client.puts(response.to_json)
529
+ rescue => e
530
+ SvelteOnRails::Lib::Utils.ssr_server_log(
531
+ "[control] handler error: #{e.class}: #{e.message}",
532
+ ssr_logfile_only: true
533
+ )
534
+ ensure
535
+ client.close rescue nil
536
+ end
537
+ end
538
+ end
539
+
540
+ at_exit do
541
+ server.close rescue nil
542
+ File.unlink(path) rescue nil
543
+ end
544
+
545
+ SvelteOnRails::Lib::Utils.ssr_server_log(
546
+ "Control listener installed on #{path}",
547
+ ssr_logfile_only: true
548
+ )
549
+
550
+ end
551
+
552
+ def self.short_env
553
+
554
+ name = Rails.env.to_s
555
+
556
+ short = {
557
+ "development" => "dev",
558
+ "production" => "prod",
559
+ "staging" => "stage",
560
+ }[name]
561
+
562
+ return name unless short
563
+ return name if Rails.root.join("config", "environments", "#{short}.rb").exist?
564
+ short
565
+ end
457
566
  end
458
567
 
459
568
  class SsrError < StandardError; end
@@ -0,0 +1,10 @@
1
+ namespace :svelte_on_rails do
2
+ desc "Precompile Svelte components for server side rendering"
3
+ task :precompile do
4
+ SvelteOnRails::Configuration.instance.vite_build
5
+ end
6
+
7
+
8
+
9
+ end
10
+ Rake::Task["assets:precompile"].enhance ["svelte_on_rails:precompile"]
@@ -0,0 +1,111 @@
1
+ namespace :svelte_on_rails do
2
+
3
+ desc "Check svelte-on-rails setup and SSR server status"
4
+ task :check do
5
+
6
+ @line_width = 130
7
+
8
+ # SSR CLIENT STATUS
9
+
10
+ cs = get_from_ssr_client(:status)
11
+ exit(1) unless cs
12
+ client_status = JSON.parse(cs).symbolize_keys
13
+ configs = SvelteOnRails::Configuration.instance
14
+ socket_path = client_status[:socket_path]
15
+
16
+ puts
17
+ puts '=' * @line_width
18
+ puts
19
+ puts "INFO FROM THE SSR CLIENT (Rails-side):"
20
+ puts
21
+ puts " Rails env : #{client_status[:env]}"
22
+ puts " Initialized by : #{client_status[:initialized_by]}"
23
+ puts " Uptime : #{client_status[:uptime].round(0)} sec"
24
+ puts ' Node version : ' + configs.node_version_test
25
+ puts ' Node bin : ' + configs.node_bin_path.to_s
26
+ puts ' NPM package version : ' + configs.npm_package_version_test.to_s
27
+ puts ' SSR Server bin : ' + configs.ssr_server_bin_path.to_s
28
+ puts " Socket (SsrServer) : #{socket_path} (#{File.socket?(socket_path) ? 'Present/OK' : 'NOT FOUND!'})"
29
+ puts " Ping to SsrServer : #{client_status[:ping] ? 'SUCCESS' : 'FAILED'}"
30
+ puts " SSR renders : #{client_status[:render_success_count]} succeeded, #{client_status[:render_failure_count]} failed"
31
+ puts
32
+
33
+ # NODE SERVER STATUS
34
+
35
+ curl_installed = system('which curl > /dev/null 2>&1')
36
+ if !curl_installed
37
+ puts '*' * @line_width
38
+ puts ' ERROR'
39
+ puts ' curl is not installed on this machine.'
40
+ puts ' unable to check node server status.'
41
+ puts '*' * @line_width
42
+ exit(1)
43
+ else
44
+ puts '=' * @line_width
45
+ puts
46
+ puts "SSR SERVER (NODE) STATUS:"
47
+ puts
48
+ curl_cmd = "curl --unix-socket #{socket_path} http://localhost"
49
+ stdout, stderr, status = Open3.capture3(curl_cmd)
50
+ puts stdout
51
+ puts
52
+ end
53
+
54
+ puts '=' * @line_width
55
+ puts
56
+ puts 'ADDITIONAL INFO:'
57
+ puts
58
+ if client_status[:error_log] == []
59
+ puts 'No error logs on the client side (Ruby).'
60
+ else
61
+ puts "#{client_status[:error_log].length} Errors (Limited to 50):"
62
+ puts " #{client_status[:error_log].reverse.join("\n ")}"
63
+ end
64
+ puts
65
+ puts 'How was the node bin path determined:'
66
+ configs.instance_variable_get(:@node_bin_path_log).each_with_index do |line, index|
67
+ puts " #{index + 1} #{line}"
68
+ end
69
+ puts
70
+ puts 'Commands:'
71
+ puts " • start node server (node path/to/svelte-ssr-server.js path/to/socket.sock):"
72
+ puts " • #{client_status[:start_node_server_command]}"
73
+ puts
74
+ puts '=' * @line_width
75
+ puts
76
+
77
+ end
78
+
79
+ namespace :ssr_client do
80
+
81
+ desc 'For development: to simulate when a client did not start on deploy (Then, on the first request of a svelte component - if not cached! - it must start)'
82
+ task :stop do
83
+ r = get_from_ssr_client(:stop)
84
+ if r['success']
85
+ puts "SSR client stopped successfully."
86
+ else
87
+ puts "ERROR: #{r}"
88
+ end
89
+ end
90
+
91
+ end
92
+
93
+ def get_from_ssr_client(request)
94
+
95
+ raise "invalid request" unless [:status, :stop].include?(request)
96
+
97
+ path = SvelteOnRails::SsrClientControlChannel.socket_path.to_path
98
+ unless File.socket?(path)
99
+ puts "*" * @line_width
100
+ puts "ERROR: Control socket not found at #{path} — is Rails running?"
101
+ puts "*" * @line_width
102
+ return
103
+ end
104
+
105
+ sock = UNIXSocket.new(path)
106
+ sock.puts(request)
107
+ response = sock.gets
108
+ sock.close
109
+ response
110
+ end
111
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: svelte-on-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 22.4.5
4
+ version: 22.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Sedlmair
@@ -102,7 +102,8 @@ files:
102
102
  - lib/svelte_on_rails/turbo_stream.rb
103
103
  - lib/svelte_on_rails/view_helpers.rb
104
104
  - lib/tasks/cache_tasks.rake
105
- - lib/tasks/svelte_on_rails_tasks.rake
105
+ - lib/tasks/general_tasks.rake
106
+ - lib/tasks/ssr_client_tasks.rake
106
107
  - templates/config_base/app/frontend/ssr/ssr.js
107
108
  - templates/config_base/config/svelte_on_rails.yml
108
109
  - templates/config_base/tsconfig.json
@@ -1,108 +0,0 @@
1
- namespace :svelte_on_rails do
2
- desc "Precompile Svelte components for server side rendering"
3
- task :precompile do
4
- SvelteOnRails::Configuration.instance.vite_build
5
- end
6
-
7
- desc "check ssr requirements and status"
8
- task :check do
9
-
10
- line_width = 100
11
- configs = SvelteOnRails::Configuration.instance
12
- ppid_file = Rails.root.join("tmp", "pids", 'svelte_on_rails.ppid')
13
- ppid = (ppid_file.exist? ? File.read(ppid_file).chomp : nil)
14
- server_helpers = SvelteOnRails::SsrServerHelpers.new
15
- socket_path = server_helpers.socket_path(ppid: ppid)
16
-
17
- unless File.socket?(socket_path)
18
- puts "*" * 80
19
- puts "ERROR: socket file does not exist or is not a socket: #{socket_path}"
20
- puts "Do you have set RAILS_ENV correctly?"
21
- puts "*" * 80
22
- end
23
-
24
- unless ppid_file.exist?
25
- puts "*" * 80
26
- puts "ERROR: PPID File does not exist: #{ppid_file}"
27
- puts "Is the app running?"
28
- puts "*" * 80
29
- end
30
-
31
- curl_installed = system('which curl > /dev/null 2>&1')
32
- if curl_installed
33
- curl_cmd = "curl --unix-socket #{socket_path} http://localhost"
34
- stdout, stderr, status = Open3.capture3(curl_cmd + '/health')
35
- srv_details = (JSON.parse(stdout) rescue {})
36
- end
37
-
38
- puts
39
- puts '=' * line_width
40
- puts "Svelte on Rails SSR Server Status:"
41
- puts " Rails env: #{Rails.env}"
42
- if srv_details['uptime']
43
- puts " Uptime: #{srv_details['uptime'].round(0)}sec"
44
- else
45
- puts " Status: not running"
46
- end
47
- puts '=' * line_width
48
- puts
49
- puts 'Node'
50
- puts label('Node bin') + configs.node_bin_path.to_s
51
- puts label('Node version') + configs.node_version_test
52
- puts label('SSR Server bin') + configs.ssr_server_bin_path.to_s
53
- puts label('SSR Server version') + configs.npm_package_version_test.to_s
54
- puts
55
- puts 'Rails instance'
56
- puts label('Process.ppid') + ppid.to_s
57
- puts label('Socket') + socket_path.to_s
58
- puts
59
- puts '=' * line_width
60
- puts
61
- puts 'MORE DETAILS:'
62
- puts
63
- puts 'How was the node bin path determined:'
64
- configs.instance_variable_get(:@node_bin_path_log).each do |line|
65
- puts " • #{line}"
66
- puts " • kill #{line}"
67
- end
68
- puts
69
- puts 'Commands:'
70
- puts " • node path/to/svelte-ssr-server.js path/to/socket.sock:"
71
- puts " • #{server_helpers.start_node_server_command(nil)}"
72
- if srv_details['pid']
73
- puts " • #{curl_cmd}"
74
- puts " • kill #{srv_details['pid']}" if srv_details['pid']
75
- end
76
- puts
77
-
78
- puts '=' * line_width
79
- puts
80
- puts 'NODE INSTANCE INFO:'
81
- puts ' (output of the above mentioned curl command)'
82
- puts
83
- puts '-' * line_width
84
- if curl_installed
85
- stdout, stderr, status = Open3.capture3(curl_cmd)
86
- if status.success?
87
- puts stdout
88
- else
89
- puts "curl exited with status #{status.exitstatus}"
90
- puts stderr unless stderr.to_s.strip.empty?
91
- end
92
- else
93
- puts "curl is not installed on this machine, skipping execution."
94
- end
95
- puts
96
- puts '=' * line_width
97
- puts
98
-
99
- end
100
-
101
- def label(msg)
102
- l = msg.length
103
- " #{msg}:"[0..20] + (msg.length > 22 ? ':' : '') + ' ' * (20 - l)
104
- end
105
-
106
- end
107
-
108
- Rake::Task["assets:precompile"].enhance ["svelte_on_rails:precompile"]