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 +4 -4
- data/lib/svelte_on_rails/lib/boot_context.rb +43 -76
- data/lib/svelte_on_rails/lib/view_helper_support.rb +1 -1
- data/lib/svelte_on_rails/railtie.rb +3 -2
- data/lib/svelte_on_rails/ssr_client.rb +212 -103
- data/lib/tasks/general_tasks.rake +10 -0
- data/lib/tasks/ssr_client_tasks.rake +111 -0
- metadata +3 -2
- data/lib/tasks/svelte_on_rails_tasks.rake +0 -108
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1bcf2e7619271bc0d415deb797d15d3d7625640171fa02485fedded0bb526ac6
|
|
4
|
+
data.tar.gz: b7c3acb70f336fb7c9ff4b203474ed584c9ba280e7a080b959d50dc73e82e9e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
|
11
|
-
return true if ENV["SVELTE_ON_RAILS_FORCE_SSR"]
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
#
|
|
30
|
-
|
|
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 "
|
|
36
|
-
return "
|
|
37
|
-
return "
|
|
38
|
-
return "rails
|
|
39
|
-
return "rails
|
|
40
|
-
return "
|
|
41
|
-
|
|
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
|
|
64
|
-
defined?(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
83
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
29
|
+
@initialized_by = caller
|
|
24
30
|
|
|
25
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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 >
|
|
414
|
+
# Fallback: socket paths > 103 chars are unreliable on macOS/BSD
|
|
362
415
|
FileUtils.rm_f(app_sock) if File.exist?(app_sock)
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,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
|
+
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/
|
|
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"]
|