morio_bridge 0.1.0
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 +7 -0
- data/bin/post-install.sh +58 -0
- data/lib/morio_bridge.rb +482 -0
- data/lib/rubygems_plugin.rb +31 -0
- data/morio_bridge.gemspec +28 -0
- metadata +46 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f0fce2880ec932e61fa2a9d3d80633b0c9da60ec447e045648ffd1dab279515e
|
4
|
+
data.tar.gz: 5fd1cb1b6e08879d422ea65f7628ecd3853da98e97697055144bf0feaedab86d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 706bb1b76e072fb7cb5f4ecdf20e7e06bfc1f2ee074df488047bebe8970c4bdf05158598a4659b1af284039f1bf6911e6a8d052469749a29023a7fdc7dd80ff7
|
7
|
+
data.tar.gz: 51c0fc4554e5a12871e36f400c1a15b317c4a7d12fe3cf9c193c1ec19f3483a6180775c3d3f49c0d110217d954828d502bf397d4b7b645692dca52a69b854dec
|
data/bin/post-install.sh
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
|
4
|
+
#===============================================
|
5
|
+
#
|
6
|
+
# Install dependencies
|
7
|
+
#
|
8
|
+
#===============================================
|
9
|
+
# Install Bun
|
10
|
+
curl -fsSL https://bun.sh/install | bash -s "bun-v1.2.13"
|
11
|
+
|
12
|
+
|
13
|
+
# install global dependencies
|
14
|
+
bun install -g prisma@6.8.2 @prisma/client@6.8.2 @prisma/adapter-pg@6.8.2 fastest-validator@1.19.1 pg@8.16.0
|
15
|
+
|
16
|
+
# add bun to the path and save it to the .bashrc and source it
|
17
|
+
export PATH="$HOME/.bun/bin:$PATH"
|
18
|
+
echo "export PATH=\"$HOME/.bun/bin:$PATH\"" >> ~/.bashrc
|
19
|
+
source ~/.bashrc
|
20
|
+
|
21
|
+
# upgrade bun to the latest version
|
22
|
+
bun upgrade
|
23
|
+
|
24
|
+
# Verify installation
|
25
|
+
echo "dependencies installed successfully"
|
26
|
+
echo "installed bun v$(bun --version)"
|
27
|
+
|
28
|
+
|
29
|
+
#===============================================
|
30
|
+
#
|
31
|
+
# Build plugins
|
32
|
+
#
|
33
|
+
#===============================================
|
34
|
+
echo "building plugins ..."
|
35
|
+
|
36
|
+
mkdir -p $HOME/.morio/bin
|
37
|
+
rm -rf $HOME/.morio/bin/server-*
|
38
|
+
rm -rf bin/server-*
|
39
|
+
|
40
|
+
IS_DOCKER="$1"
|
41
|
+
PLATFORM="$2"
|
42
|
+
ARCH="$3"
|
43
|
+
OUTPUT=$HOME/.morio/bin/bridge_server.js
|
44
|
+
|
45
|
+
|
46
|
+
cd ./server
|
47
|
+
|
48
|
+
bun install
|
49
|
+
bun build --target=bun index.ts --outfile "$OUTPUT"
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
|
data/lib/morio_bridge.rb
ADDED
@@ -0,0 +1,482 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "pathname"
|
5
|
+
require "fileutils"
|
6
|
+
require "tmpdir"
|
7
|
+
require "socket"
|
8
|
+
require "json"
|
9
|
+
require "logger"
|
10
|
+
|
11
|
+
class Error < StandardError; end
|
12
|
+
class TimeoutError < Error; end
|
13
|
+
|
14
|
+
# Main module for the gem
|
15
|
+
module MorioBridge
|
16
|
+
VERSION = "0.1.0"
|
17
|
+
|
18
|
+
class Doctor
|
19
|
+
def self.run
|
20
|
+
stats = JSON.pretty_generate({
|
21
|
+
current_dir: Dir.pwd,
|
22
|
+
bun_path: `which bun`.strip,
|
23
|
+
bun_version: `bun --version`.strip
|
24
|
+
})
|
25
|
+
|
26
|
+
puts stats
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Client
|
31
|
+
attr_reader :instance
|
32
|
+
|
33
|
+
def initialize(base_socket_dir = nil)
|
34
|
+
@server_path = File.expand_path(File.join(Dir.home, ".morio/bin/bridge_server.js"))
|
35
|
+
@bun_path = File.expand_path(File.join(Dir.home, ".bun/bin/bun"))
|
36
|
+
@base_socket_dir = base_socket_dir || Dir.mktmpdir("prisma")
|
37
|
+
@socket_paths = fetch_socket_paths(6)
|
38
|
+
@socket_connections = []
|
39
|
+
@socket_status = []
|
40
|
+
@socket_index = 0
|
41
|
+
@pid = nil
|
42
|
+
@started = false
|
43
|
+
@starting = false
|
44
|
+
@instance = nil
|
45
|
+
@logger = Logger.new($stdout)
|
46
|
+
|
47
|
+
return unless !@testing_mode && defined?(Morio)
|
48
|
+
|
49
|
+
# morio global logger
|
50
|
+
@logger = Morio.logger
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# [ Lifecycle ]
|
55
|
+
#
|
56
|
+
def start
|
57
|
+
@starting = true
|
58
|
+
|
59
|
+
begin
|
60
|
+
FileUtils.mkdir_p(@base_socket_dir)
|
61
|
+
FileUtils.chmod(0o700, @base_socket_dir)
|
62
|
+
|
63
|
+
env = {
|
64
|
+
"MORIO_RB_BUN_SOCKETS" => @socket_paths.join(","),
|
65
|
+
"MORIO_RB_PWD" => Dir.pwd
|
66
|
+
}
|
67
|
+
|
68
|
+
# Start server in background thread immediately
|
69
|
+
server_thread = Thread.new do
|
70
|
+
# Create pipes for stdout and stderr
|
71
|
+
out_read, out_write = IO.pipe
|
72
|
+
err_read, err_write = IO.pipe
|
73
|
+
|
74
|
+
@pid = Process.spawn(
|
75
|
+
env,
|
76
|
+
"#{@bun_path} #{@server_path}",
|
77
|
+
err: err_write,
|
78
|
+
out: out_write,
|
79
|
+
pgroup: true,
|
80
|
+
close_others: true
|
81
|
+
)
|
82
|
+
|
83
|
+
# Close write ends in parent since they are not needed as we dont write anything to the child process
|
84
|
+
out_write.close
|
85
|
+
err_write.close
|
86
|
+
|
87
|
+
log_server_output(out_read, err_read)
|
88
|
+
|
89
|
+
Process.detach(@pid)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Start health check in parallel
|
93
|
+
health_thread = Thread.new { server_healthy? }
|
94
|
+
|
95
|
+
# Wait for both to complete
|
96
|
+
server_thread.join
|
97
|
+
health_thread.join
|
98
|
+
|
99
|
+
# Initialize sockets...
|
100
|
+
threads = @socket_paths.map do |socket_path|
|
101
|
+
Thread.new do
|
102
|
+
socket_healthy?(socket_path)
|
103
|
+
socket = UNIXSocket.new(socket_path)
|
104
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
|
105
|
+
[socket, true]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
@socket_connections, @socket_status = threads.map(&:value).transpose
|
109
|
+
|
110
|
+
@started = true
|
111
|
+
|
112
|
+
# save instance
|
113
|
+
@instance = self
|
114
|
+
|
115
|
+
at_exit { stop }
|
116
|
+
ensure
|
117
|
+
@starting = false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def stop
|
122
|
+
@logger.debug "Stopping morio_bridge ..."
|
123
|
+
|
124
|
+
# close all the socket connections
|
125
|
+
@socket_connections.each do |connection|
|
126
|
+
connection.close
|
127
|
+
rescue StandardError
|
128
|
+
nil
|
129
|
+
end
|
130
|
+
|
131
|
+
# kill the unix socket cluster process
|
132
|
+
Process.kill("TERM", @pid) if @pid
|
133
|
+
|
134
|
+
# remove the socket directory
|
135
|
+
FileUtils.remove_entry(@base_socket_dir) if File.directory?(@base_socket_dir)
|
136
|
+
|
137
|
+
# set the started flag to false
|
138
|
+
@started = false
|
139
|
+
|
140
|
+
@logger.debug "Stopped the morio_bridge"
|
141
|
+
rescue StandardError
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# [ Message Handler ]
|
147
|
+
#
|
148
|
+
def request(data, skip_client_start: false)
|
149
|
+
# start the client if it's not already started
|
150
|
+
start unless @started || skip_client_start
|
151
|
+
|
152
|
+
# set the max number of retries
|
153
|
+
max_retries = 3
|
154
|
+
retries = 0
|
155
|
+
timeout = 0.05
|
156
|
+
|
157
|
+
loop do
|
158
|
+
# select a socket in round robin fashion
|
159
|
+
socket = select_socket
|
160
|
+
|
161
|
+
begin
|
162
|
+
# raise an error if the socket is closed
|
163
|
+
raise IOError, "Socket is closed" if socket.closed?
|
164
|
+
|
165
|
+
#=====================================
|
166
|
+
#
|
167
|
+
# Send Request
|
168
|
+
#
|
169
|
+
#=====================================
|
170
|
+
# generate the request body
|
171
|
+
request = to_socket_format(data)
|
172
|
+
|
173
|
+
# write the request to the socket
|
174
|
+
socket.write_nonblock(request)
|
175
|
+
|
176
|
+
#=====================================
|
177
|
+
#
|
178
|
+
# Read Response
|
179
|
+
#
|
180
|
+
#=====================================
|
181
|
+
# wait for the socket to be readable
|
182
|
+
readable = socket.wait_readable(timeout)
|
183
|
+
|
184
|
+
# raise an error if the socket is not readable
|
185
|
+
unless readable
|
186
|
+
timeout = 0.002
|
187
|
+
raise TimeoutError, "Command timed out"
|
188
|
+
end
|
189
|
+
|
190
|
+
# read the response from the socket into a string buffer
|
191
|
+
buffer = String.new(capacity: 4096)
|
192
|
+
done = false
|
193
|
+
|
194
|
+
while (chunk = socket.readpartial(4096))
|
195
|
+
buffer << chunk
|
196
|
+
done = true if chunk.include?("\r\n\r\n")
|
197
|
+
break if done
|
198
|
+
end
|
199
|
+
|
200
|
+
#=====================================
|
201
|
+
#
|
202
|
+
# Parse Response
|
203
|
+
#
|
204
|
+
#=====================================
|
205
|
+
body = to_hash(from_socket_format(buffer))
|
206
|
+
return body
|
207
|
+
rescue Errno::EPIPE, IOError, TimeoutError => _e
|
208
|
+
# retry the connection in case of a communication error
|
209
|
+
retry_failed_connection(socket)
|
210
|
+
|
211
|
+
# increment the retries
|
212
|
+
retries += 1
|
213
|
+
|
214
|
+
# raise an error if the retries exceed the max retries
|
215
|
+
raise Error, "No healthy sockets after #{max_retries} retries" if retries >= max_retries
|
216
|
+
|
217
|
+
# sleep for a short period before retrying - exponential backoff
|
218
|
+
sleep(0.05 * (2**retries))
|
219
|
+
retry
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
#
|
225
|
+
# [ Benchmark ]
|
226
|
+
#
|
227
|
+
def benchmark(send_rate_per_second: 600_000, requests_to_be_sent: 10_000)
|
228
|
+
basic_test = lambda do
|
229
|
+
request({ command: "create", body: { name: "John Doe" } })
|
230
|
+
end
|
231
|
+
|
232
|
+
speed_test = lambda do
|
233
|
+
error_count = 0
|
234
|
+
durations = []
|
235
|
+
|
236
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
237
|
+
|
238
|
+
loop_count = 0
|
239
|
+
|
240
|
+
loop do
|
241
|
+
break if loop_count > requests_to_be_sent
|
242
|
+
|
243
|
+
begin
|
244
|
+
duration, _result = UnixClient.with_timing(&basic_test)
|
245
|
+
durations << duration
|
246
|
+
rescue Error => _e
|
247
|
+
error_count += 1
|
248
|
+
end
|
249
|
+
|
250
|
+
loop_count += 1
|
251
|
+
|
252
|
+
sleep(1.0 / send_rate_per_second)
|
253
|
+
end
|
254
|
+
|
255
|
+
duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - start) / 1_000_000.0
|
256
|
+
rps = requests_to_be_sent / duration_ms * 1000
|
257
|
+
|
258
|
+
{
|
259
|
+
requests: requests_to_be_sent,
|
260
|
+
send_rate_per_second: send_rate_per_second,
|
261
|
+
duration_ms: duration_ms.round(2),
|
262
|
+
avg_latency_ms: (durations.sum / durations.size).round(2),
|
263
|
+
rps: rps.round(0),
|
264
|
+
errors: error_count
|
265
|
+
}
|
266
|
+
end
|
267
|
+
|
268
|
+
# Run tests in parallel
|
269
|
+
speed_thread = Thread.new { UnixClient.with_timing(&speed_test) }
|
270
|
+
total_duration, results = speed_thread.value
|
271
|
+
|
272
|
+
result = JSON.pretty_generate({ results:, total_duration: })
|
273
|
+
|
274
|
+
# make result gray and dim and log it
|
275
|
+
@logger.info result
|
276
|
+
end
|
277
|
+
|
278
|
+
#
|
279
|
+
# [ Benchmark helpers ]
|
280
|
+
#
|
281
|
+
def self.with_timing(&proc)
|
282
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
283
|
+
result = proc.call
|
284
|
+
end_ns = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
285
|
+
duration_ms = ((end_ns - start) / 1_000_000.0)
|
286
|
+
|
287
|
+
[duration_ms, result]
|
288
|
+
end
|
289
|
+
|
290
|
+
private
|
291
|
+
|
292
|
+
#
|
293
|
+
# [ Child process Logging helpers ]
|
294
|
+
#
|
295
|
+
def log_server_output(out_read, err_read)
|
296
|
+
# Start threads to read from pipes
|
297
|
+
Thread.new { handle_server_stream(out_read, :info) }
|
298
|
+
Thread.new { handle_server_stream(err_read, :error) }
|
299
|
+
end
|
300
|
+
|
301
|
+
def handle_server_stream(stream, log_level)
|
302
|
+
buffer = String.new
|
303
|
+
while (line = stream.gets)
|
304
|
+
buffer << line
|
305
|
+
next unless line.end_with?("\n")
|
306
|
+
|
307
|
+
message = buffer.chomp
|
308
|
+
# First try as single JSON line
|
309
|
+
begin
|
310
|
+
parsed = JSON.parse(message)
|
311
|
+
@logger.send(log_level, JSON.pretty_generate(parsed))
|
312
|
+
buffer.clear
|
313
|
+
rescue JSON::ParserError
|
314
|
+
# If single line parse failed, check if we have complete JSON
|
315
|
+
begin
|
316
|
+
parsed = JSON.parse(buffer)
|
317
|
+
@logger.send(log_level, JSON.pretty_generate(parsed))
|
318
|
+
buffer.clear
|
319
|
+
rescue JSON::ParserError
|
320
|
+
# Not complete JSON yet, or plain text
|
321
|
+
# Only log and clear if it's definitely not JSON
|
322
|
+
|
323
|
+
# turn off Metrics/BlockNesting:
|
324
|
+
# rubocop:disable Metrics/BlockNesting
|
325
|
+
unless buffer.match?(/[\{\[]/u)
|
326
|
+
@logger.send(log_level, message)
|
327
|
+
buffer.clear
|
328
|
+
end
|
329
|
+
# rubocop:enable Metrics/BlockNesting
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
# Log any remaining buffer content
|
334
|
+
@logger.send(log_level, buffer) unless buffer.empty?
|
335
|
+
end
|
336
|
+
|
337
|
+
#
|
338
|
+
# [ Socket helpers ]
|
339
|
+
#
|
340
|
+
def fetch_socket_paths(socket_server_count)
|
341
|
+
(0..(socket_server_count - 1)).map do |i|
|
342
|
+
File.join(@base_socket_dir, "prisma#{i}.sock")
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def select_socket
|
347
|
+
connection = @socket_connections[@socket_index]
|
348
|
+
|
349
|
+
@socket_index = (@socket_index + 1) % @socket_connections.length
|
350
|
+
return connection if @socket_status[@socket_connections.index(connection) || 0]
|
351
|
+
|
352
|
+
@socket_paths.length.times do
|
353
|
+
connection = @socket_connections[@socket_index]
|
354
|
+
|
355
|
+
@socket_index = (@socket_index + 1) % @socket_connections.length
|
356
|
+
return connection if @socket_status[@socket_connections.index(connection) || 0]
|
357
|
+
end
|
358
|
+
raise Error, "No healthy sockets available"
|
359
|
+
end
|
360
|
+
|
361
|
+
def retry_failed_connection(failed_socket)
|
362
|
+
# get the index of the failed socket and return if it doesn't exist
|
363
|
+
failed_connection_id = @socket_connections.index(failed_socket)
|
364
|
+
return unless failed_connection_id
|
365
|
+
|
366
|
+
# set the socket status to false marking it as unhealthy
|
367
|
+
@socket_status[failed_connection_id] = false
|
368
|
+
|
369
|
+
# close the failed socket
|
370
|
+
begin
|
371
|
+
@socket_connections[failed_connection_id].close
|
372
|
+
rescue StandardError
|
373
|
+
nil
|
374
|
+
end
|
375
|
+
|
376
|
+
# get the socket path for the failed socket
|
377
|
+
socket_path = @socket_paths[failed_connection_id]
|
378
|
+
|
379
|
+
# try to reconnect to the socket
|
380
|
+
begin
|
381
|
+
@socket_connections[failed_connection_id] = UNIXSocket.new(socket_path)
|
382
|
+
|
383
|
+
# set the socket status to true marking it as healthy if the connection is successful
|
384
|
+
@socket_status[failed_connection_id] = true
|
385
|
+
rescue Errno::ENOENT, Errno::ECONNREFUSED => e
|
386
|
+
@logger.error "Failed to reconnect to socket #{socket_path}: #{e.message}"
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
#
|
391
|
+
# [ Health check helpers ]
|
392
|
+
#
|
393
|
+
def socket_healthy?(socket_path)
|
394
|
+
max_connection_attempts = 50
|
395
|
+
connection_attempt = 0
|
396
|
+
base_delay_s = 0.05
|
397
|
+
|
398
|
+
while connection_attempt < max_connection_attempts
|
399
|
+
begin
|
400
|
+
# attempt to connect to the socket if the connection is successful, close the socket and return
|
401
|
+
socket = UNIXSocket.new(socket_path)
|
402
|
+
socket.close
|
403
|
+
return true
|
404
|
+
rescue Errno::ENOENT, Errno::ECONNREFUSED => _e
|
405
|
+
# exponential backoff
|
406
|
+
sleep(base_delay_s * (2**connection_attempt))
|
407
|
+
end
|
408
|
+
|
409
|
+
# increment the connection attempt
|
410
|
+
connection_attempt += 1
|
411
|
+
end
|
412
|
+
|
413
|
+
# raise an error if the connection attempt fails and exceeds the max connection attempts
|
414
|
+
raise TimeoutError, "Could not connect to socket #{socket_path} after #{max_connection_attempts} attempts"
|
415
|
+
end
|
416
|
+
|
417
|
+
def server_healthy?
|
418
|
+
max_connection_attempts = 50
|
419
|
+
connection_attempt = 0
|
420
|
+
base_delay_s = 0.05 # Reduced from 0.1
|
421
|
+
|
422
|
+
while connection_attempt < max_connection_attempts
|
423
|
+
begin
|
424
|
+
socket = UNIXSocket.new(@socket_paths.first)
|
425
|
+
socket.write_nonblock("GET /health HTTP/1.1\r\nContent-Length: 0\r\n\r\n")
|
426
|
+
|
427
|
+
# Reduced timeout
|
428
|
+
if socket.wait_readable(0.05)
|
429
|
+
response = socket.read_nonblock(1024) # Smaller read buffer
|
430
|
+
if response.include?("200") # Simpler check
|
431
|
+
socket.close
|
432
|
+
return true
|
433
|
+
end
|
434
|
+
end
|
435
|
+
socket.close
|
436
|
+
rescue Errno::ENOENT, Errno::ECONNREFUSED, IOError => _e
|
437
|
+
sleep(base_delay_s * (1.5**connection_attempt)) # Less aggressive backoff
|
438
|
+
end
|
439
|
+
connection_attempt += 1
|
440
|
+
end
|
441
|
+
|
442
|
+
raise TimeoutError, "Server failed to start"
|
443
|
+
end
|
444
|
+
|
445
|
+
#
|
446
|
+
# [ socket format helpers ]
|
447
|
+
#
|
448
|
+
def from_socket_format(buffer)
|
449
|
+
lines = buffer.lines
|
450
|
+
status_line = lines[0]
|
451
|
+
matches = status_line.match(%r{HTTP/1\.1 (\d+)})
|
452
|
+
status_code = matches&.captures&.first.to_i || nil
|
453
|
+
body_start = lines.index("\r\n") || lines.length
|
454
|
+
body = lines[(body_start + 1)..]&.join || ""
|
455
|
+
|
456
|
+
# raise an error if the status code is not 200
|
457
|
+
raise Error, "Executable returned #{status_code}: #{body}" unless status_code == 200
|
458
|
+
|
459
|
+
body
|
460
|
+
end
|
461
|
+
|
462
|
+
def to_socket_format(data)
|
463
|
+
body = JSON.generate(data)
|
464
|
+
|
465
|
+
[
|
466
|
+
"POST /prisma/0/call HTTP/1.1",
|
467
|
+
"Content-Type: application/json",
|
468
|
+
"Content-Length: #{body.length}",
|
469
|
+
"",
|
470
|
+
body
|
471
|
+
].join("\r\n")
|
472
|
+
end
|
473
|
+
|
474
|
+
def to_hash(body)
|
475
|
+
# Validate JSON response
|
476
|
+
|
477
|
+
JSON.parse(body)
|
478
|
+
rescue JSON::ParserError => e
|
479
|
+
raise Error, "Invalid JSON response: #{e.message}, body: #{body.inspect}"
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "morio_bridge"
|
5
|
+
require "English"
|
6
|
+
|
7
|
+
Gem.post_install do |installer|
|
8
|
+
if installer.spec.name == "morio_bridge"
|
9
|
+
post_install_script_path = File.expand_path("../bin/post-install.sh", __dir__)
|
10
|
+
|
11
|
+
if File.exist?(post_install_script_path)
|
12
|
+
# Make the script executable
|
13
|
+
File.chmod(0o755, post_install_script_path)
|
14
|
+
|
15
|
+
# Add Bun to PATH
|
16
|
+
ENV["PATH"] = "#{Dir.home}/.bun/bin:#{ENV.fetch('PATH', nil)}"
|
17
|
+
|
18
|
+
# Run the script
|
19
|
+
system("bash #{post_install_script_path}")
|
20
|
+
|
21
|
+
# Check if the script ran successfully
|
22
|
+
if $CHILD_STATUS.success?
|
23
|
+
puts "post installation completed successfully"
|
24
|
+
else
|
25
|
+
puts "post installation failed with exit code #{$CHILD_STATUS.exitstatus}"
|
26
|
+
end
|
27
|
+
else
|
28
|
+
puts "Warning: Could not find post install script at #{post_install_script_path}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "morio_bridge"
|
5
|
+
spec.version = "0.1.0"
|
6
|
+
spec.authors = ["r2g"]
|
7
|
+
spec.email = ["r2g.technology@gmail.com"]
|
8
|
+
spec.summary = "A simple Ruby gem with a post-install hook."
|
9
|
+
spec.description = "This gem demonstrates creating a Ruby gem with a post-install hook that installs Bun."
|
10
|
+
spec.homepage = "https://github.com/r2g/morio-bridge"
|
11
|
+
spec.license = "MIT"
|
12
|
+
|
13
|
+
# Specify which files to include in the gem
|
14
|
+
spec.files = Dir[
|
15
|
+
"lib/**/*",
|
16
|
+
"bin/**/*",
|
17
|
+
"README.md",
|
18
|
+
"LICENSE.txt",
|
19
|
+
"*.gemspec"
|
20
|
+
]
|
21
|
+
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
spec.bindir = "exe"
|
24
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
25
|
+
|
26
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
27
|
+
spec.required_ruby_version = ">= 3.4.3"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: morio_bridge
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- r2g
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
12
|
+
description: This gem demonstrates creating a Ruby gem with a post-install hook that
|
13
|
+
installs Bun.
|
14
|
+
email:
|
15
|
+
- r2g.technology@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- bin/post-install.sh
|
21
|
+
- lib/morio_bridge.rb
|
22
|
+
- lib/rubygems_plugin.rb
|
23
|
+
- morio_bridge.gemspec
|
24
|
+
homepage: https://github.com/r2g/morio-bridge
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata:
|
28
|
+
rubygems_mfa_required: 'true'
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 3.4.3
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubygems_version: 3.6.7
|
44
|
+
specification_version: 4
|
45
|
+
summary: A simple Ruby gem with a post-install hook.
|
46
|
+
test_files: []
|