tina4ruby 3.10.50 → 3.10.54
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/tina4/cli.rb +9 -2
- data/lib/tina4/cors.rb +1 -1
- data/lib/tina4/orm.rb +8 -1
- data/lib/tina4/rack_app.rb +23 -3
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/webserver.rb +108 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5f97899d98f7c34c914f158fc57c2b0253b79e544ad9feacdca10d6a6479209f
|
|
4
|
+
data.tar.gz: 8370cfeefc28525f4cd918543e51e5f444c7da5845c80e58c2dc548d513f26de
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dde7f333c0474ff7add07c7693ebbb27b8a11d565f6f031ee0aeee106cd46e3e696a70bed388ceeebd40640b7022eb68e7e12aee55668a8914c3d5ac205cee0b
|
|
7
|
+
data.tar.gz: 0da6d2ab9ea1bb916f305f5f1dc2426dd92e1a54b3ac601c5b9cc607587ca64e7b40e0141e9cee6d7889bf001647c35b52ba2d19441027fe14bba2727d53f235
|
data/lib/tina4/cli.rb
CHANGED
|
@@ -155,7 +155,7 @@ module Tina4
|
|
|
155
155
|
# ── start ─────────────────────────────────────────────────────────────
|
|
156
156
|
|
|
157
157
|
def cmd_start(argv)
|
|
158
|
-
options = { port: nil, host: nil, dev: false, no_browser: false, production: false }
|
|
158
|
+
options = { port: nil, host: nil, dev: false, no_browser: false, no_reload: false, production: false }
|
|
159
159
|
parser = OptionParser.new do |opts|
|
|
160
160
|
opts.banner = "Usage: tina4ruby start [options]"
|
|
161
161
|
opts.on("-p", "--port PORT", Integer, "Port (default: 7147)") { |v| options[:port] = v }
|
|
@@ -163,6 +163,7 @@ module Tina4
|
|
|
163
163
|
opts.on("-d", "--dev", "Enable dev mode with auto-reload") { options[:dev] = true }
|
|
164
164
|
opts.on("--production", "Use production server (Puma)") { options[:production] = true }
|
|
165
165
|
opts.on("--no-browser", "Do not open browser on start") { options[:no_browser] = true }
|
|
166
|
+
opts.on("--no-reload", "Disable file watcher / live-reload") { options[:no_reload] = true }
|
|
166
167
|
end
|
|
167
168
|
parser.parse!(argv)
|
|
168
169
|
|
|
@@ -172,6 +173,11 @@ module Tina4
|
|
|
172
173
|
options[:no_browser] = true
|
|
173
174
|
end
|
|
174
175
|
|
|
176
|
+
# --no-reload flag sets TINA4_NO_RELOAD so the existing env check picks it up
|
|
177
|
+
if options[:no_reload]
|
|
178
|
+
ENV["TINA4_NO_RELOAD"] = "true"
|
|
179
|
+
end
|
|
180
|
+
|
|
175
181
|
# Priority: CLI flag > ENV var > default
|
|
176
182
|
options[:port] = resolve_config(:port, options[:port])
|
|
177
183
|
options[:host] = resolve_config(:host, options[:host])
|
|
@@ -191,7 +197,8 @@ module Tina4
|
|
|
191
197
|
load_routes(root_dir)
|
|
192
198
|
|
|
193
199
|
if options[:dev]
|
|
194
|
-
|
|
200
|
+
no_reload = %w[true 1 yes].include?(ENV.fetch("TINA4_NO_RELOAD", "").downcase)
|
|
201
|
+
Tina4::DevReload.start(root_dir: root_dir) unless no_reload
|
|
195
202
|
Tina4::ScssCompiler.compile_all(root_dir)
|
|
196
203
|
end
|
|
197
204
|
|
data/lib/tina4/cors.rb
CHANGED
data/lib/tina4/orm.rb
CHANGED
|
@@ -18,7 +18,7 @@ module Tina4
|
|
|
18
18
|
|
|
19
19
|
class << self
|
|
20
20
|
def db
|
|
21
|
-
@db || Tina4.database
|
|
21
|
+
@db || Tina4.database || auto_discover_db
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Per-model database binding
|
|
@@ -345,6 +345,13 @@ module Tina4
|
|
|
345
345
|
|
|
346
346
|
private
|
|
347
347
|
|
|
348
|
+
def auto_discover_db
|
|
349
|
+
url = ENV["DATABASE_URL"]
|
|
350
|
+
return nil unless url
|
|
351
|
+
Tina4.database = Tina4::Database.new(url, username: ENV.fetch("DATABASE_USERNAME", ""), password: ENV.fetch("DATABASE_PASSWORD", ""))
|
|
352
|
+
Tina4.database
|
|
353
|
+
end
|
|
354
|
+
|
|
348
355
|
def find_by_id(id)
|
|
349
356
|
pk = primary_key_field || :id
|
|
350
357
|
sql = "SELECT * FROM #{table_name} WHERE #{pk} = ?"
|
data/lib/tina4/rack_app.rb
CHANGED
|
@@ -3,6 +3,19 @@ require "json"
|
|
|
3
3
|
require "securerandom"
|
|
4
4
|
|
|
5
5
|
module Tina4
|
|
6
|
+
# Middleware wrapper that tags requests arriving on the AI dev port.
|
|
7
|
+
# Suppresses live-reload behaviour so AI tools get stable responses.
|
|
8
|
+
class AiPortRackApp
|
|
9
|
+
def initialize(app)
|
|
10
|
+
@app = app
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(env)
|
|
14
|
+
env["tina4.ai_port"] = true
|
|
15
|
+
@app.call(env)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
6
19
|
class RackApp
|
|
7
20
|
STATIC_DIRS = %w[public src/public src/assets assets].freeze
|
|
8
21
|
|
|
@@ -43,6 +56,10 @@ module Tina4
|
|
|
43
56
|
|
|
44
57
|
# Dev dashboard routes (handled before anything else)
|
|
45
58
|
if path.start_with?("/__dev")
|
|
59
|
+
# Block live-reload endpoint on the AI port — AI tools must get stable responses
|
|
60
|
+
if path == "/__dev_reload" && env["tina4.ai_port"]
|
|
61
|
+
return [404, { "content-type" => "text/plain" }, ["Not available on AI port"]]
|
|
62
|
+
end
|
|
46
63
|
dev_response = Tina4::DevAdmin.handle_request(env)
|
|
47
64
|
return dev_response if dev_response
|
|
48
65
|
end
|
|
@@ -95,7 +112,7 @@ module Tina4
|
|
|
95
112
|
matched_pattern: matched_pattern || "(no match)",
|
|
96
113
|
}
|
|
97
114
|
joined = body_parts.join
|
|
98
|
-
overlay = inject_dev_overlay(joined, request_info)
|
|
115
|
+
overlay = inject_dev_overlay(joined, request_info, ai_port: env["tina4.ai_port"])
|
|
99
116
|
rack_response = [status, headers, [overlay]]
|
|
100
117
|
end
|
|
101
118
|
end
|
|
@@ -630,7 +647,7 @@ module Tina4
|
|
|
630
647
|
[-1, {}, []]
|
|
631
648
|
end
|
|
632
649
|
|
|
633
|
-
def inject_dev_overlay(body, request_info)
|
|
650
|
+
def inject_dev_overlay(body, request_info, ai_port: false)
|
|
634
651
|
version = Tina4::VERSION
|
|
635
652
|
method = request_info[:method]
|
|
636
653
|
path = request_info[:path]
|
|
@@ -638,9 +655,11 @@ module Tina4
|
|
|
638
655
|
request_id = Tina4::Log.request_id || "-"
|
|
639
656
|
route_count = Tina4::Router.routes.length
|
|
640
657
|
|
|
658
|
+
ai_badge = ai_port ? '<span style="background:#7c3aed;color:#fff;font-size:10px;padding:1px 6px;border-radius:3px;font-weight:bold;">AI PORT</span>' : ""
|
|
659
|
+
|
|
641
660
|
toolbar = <<~HTML.strip
|
|
642
661
|
<div id="tina4-dev-toolbar" style="position:fixed;bottom:0;left:0;right:0;background:#333;color:#fff;font-family:monospace;font-size:12px;padding:6px 16px;z-index:99999;display:flex;align-items:center;gap:16px;">
|
|
643
|
-
<span id="tina4-ver-btn" style="color:#d32f2f;font-weight:bold;cursor:pointer;text-decoration:underline dotted;" onclick="tina4VersionModal()" title="Click to check for updates">Tina4 v#{version}</span>
|
|
662
|
+
#{ai_badge}<span id="tina4-ver-btn" style="color:#d32f2f;font-weight:bold;cursor:pointer;text-decoration:underline dotted;" onclick="tina4VersionModal()" title="Click to check for updates">Tina4 v#{version}</span>
|
|
644
663
|
<div id="tina4-ver-modal" style="display:none;position:fixed;bottom:3rem;left:1rem;background:#1e1e2e;border:1px solid #d32f2f;border-radius:8px;padding:16px 20px;z-index:100000;min-width:320px;box-shadow:0 8px 32px rgba(0,0,0,0.5);font-family:monospace;font-size:13px;color:#cdd6f4;">
|
|
645
664
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
646
665
|
<strong style="color:#89b4fa;">Version Info</strong>
|
|
@@ -709,6 +728,7 @@ module Tina4
|
|
|
709
728
|
el.style.color='#f38ba8';
|
|
710
729
|
});
|
|
711
730
|
}
|
|
731
|
+
#{ai_port ? "" : "/* tina4:reload-js */"}
|
|
712
732
|
</script>
|
|
713
733
|
HTML
|
|
714
734
|
|
data/lib/tina4/version.rb
CHANGED
data/lib/tina4/webserver.rb
CHANGED
|
@@ -11,6 +11,7 @@ module Tina4
|
|
|
11
11
|
def start
|
|
12
12
|
require "webrick"
|
|
13
13
|
require "stringio"
|
|
14
|
+
require "socket"
|
|
14
15
|
Tina4.print_banner(host: @host, port: @port)
|
|
15
16
|
Tina4::Log.info("Starting Tina4 WEBrick server on http://#{@host}:#{@port}")
|
|
16
17
|
@server = WEBrick::HTTPServer.new(
|
|
@@ -101,10 +102,117 @@ module Tina4
|
|
|
101
102
|
servlet.define_method(:webrick_req_port) { port }
|
|
102
103
|
|
|
103
104
|
@server.mount("/", servlet, rack_app)
|
|
105
|
+
|
|
106
|
+
# AI dev port (port + 1) — no-reload, no-browser
|
|
107
|
+
@ai_server = nil
|
|
108
|
+
@ai_thread = nil
|
|
109
|
+
no_ai_port = %w[true 1 yes].include?(ENV.fetch("TINA4_NO_AI_PORT", "").downcase)
|
|
110
|
+
is_debug = %w[true 1 yes].include?(ENV.fetch("TINA4_DEBUG", "").downcase)
|
|
111
|
+
|
|
112
|
+
if is_debug && !no_ai_port
|
|
113
|
+
ai_port = @port + 1
|
|
114
|
+
begin
|
|
115
|
+
test = TCPServer.new("0.0.0.0", ai_port)
|
|
116
|
+
test.close
|
|
117
|
+
|
|
118
|
+
@ai_server = WEBrick::HTTPServer.new(
|
|
119
|
+
BindAddress: @host,
|
|
120
|
+
Port: ai_port,
|
|
121
|
+
Logger: WEBrick::Log.new(File::NULL),
|
|
122
|
+
AccessLog: []
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Wrap the rack app so AI-port requests are tagged
|
|
126
|
+
ai_rack_app = Tina4::AiPortRackApp.new(@app)
|
|
127
|
+
|
|
128
|
+
# Build a servlet identical to the main one but bound to the AI port host/port
|
|
129
|
+
ai_host = @host
|
|
130
|
+
ai_port_str = ai_port.to_s
|
|
131
|
+
ai_servlet = Class.new(WEBrick::HTTPServlet::AbstractServlet) do
|
|
132
|
+
define_method(:initialize) do |server, app|
|
|
133
|
+
super(server)
|
|
134
|
+
@app = app
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
%w[GET POST PUT DELETE PATCH HEAD OPTIONS].each do |http_method|
|
|
138
|
+
define_method("do_#{http_method}") do |webrick_req, webrick_res|
|
|
139
|
+
handle_request(webrick_req, webrick_res)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
define_method(:handle_request) do |webrick_req, webrick_res|
|
|
144
|
+
if Tina4::Shutdown.shutting_down?
|
|
145
|
+
webrick_res.status = 503
|
|
146
|
+
webrick_res.body = '{"error":"Service shutting down"}'
|
|
147
|
+
webrick_res["content-type"] = "application/json"
|
|
148
|
+
return
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
Tina4::Shutdown.track_request do
|
|
152
|
+
env = build_rack_env(webrick_req)
|
|
153
|
+
status, headers, body = @app.call(env)
|
|
154
|
+
|
|
155
|
+
webrick_res.status = status
|
|
156
|
+
headers.each do |key, value|
|
|
157
|
+
if key.downcase == "set-cookie"
|
|
158
|
+
Array(value.split("\n")).each { |c| webrick_res.cookies << WEBrick::Cookie.parse_set_cookie(c) }
|
|
159
|
+
else
|
|
160
|
+
webrick_res[key] = value
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
response_body = ""
|
|
165
|
+
body.each { |chunk| response_body += chunk }
|
|
166
|
+
webrick_res.body = response_body
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
define_method(:build_rack_env) do |req|
|
|
171
|
+
input = StringIO.new(req.body || "")
|
|
172
|
+
env = {
|
|
173
|
+
"REQUEST_METHOD" => req.request_method,
|
|
174
|
+
"PATH_INFO" => req.path,
|
|
175
|
+
"QUERY_STRING" => req.query_string || "",
|
|
176
|
+
"SERVER_NAME" => webrick_req_host,
|
|
177
|
+
"SERVER_PORT" => webrick_req_port,
|
|
178
|
+
"CONTENT_TYPE" => req.content_type || "",
|
|
179
|
+
"CONTENT_LENGTH" => (req.content_length rescue 0).to_s,
|
|
180
|
+
"REMOTE_ADDR" => req.peeraddr&.last || "127.0.0.1",
|
|
181
|
+
"rack.input" => input,
|
|
182
|
+
"rack.errors" => $stderr,
|
|
183
|
+
"rack.url_scheme" => "http",
|
|
184
|
+
"rack.version" => [1, 3],
|
|
185
|
+
"rack.multithread" => true,
|
|
186
|
+
"rack.multiprocess" => false,
|
|
187
|
+
"rack.run_once" => false
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
req.header.each do |key, values|
|
|
191
|
+
env_key = "HTTP_#{key.upcase.gsub('-', '_')}"
|
|
192
|
+
env[env_key] = values.join(", ")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
env
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
ai_servlet.define_method(:webrick_req_host) { ai_host }
|
|
200
|
+
ai_servlet.define_method(:webrick_req_port) { ai_port_str }
|
|
201
|
+
|
|
202
|
+
@ai_server.mount("/", ai_servlet, ai_rack_app)
|
|
203
|
+
@ai_thread = Thread.new { @ai_server.start }
|
|
204
|
+
puts " AI Port: http://localhost:#{ai_port} (no-reload)"
|
|
205
|
+
rescue Errno::EADDRINUSE
|
|
206
|
+
puts " AI Port: SKIPPED (port #{ai_port} in use)"
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
104
210
|
@server.start
|
|
105
211
|
end
|
|
106
212
|
|
|
107
213
|
def stop
|
|
214
|
+
@ai_server&.shutdown
|
|
215
|
+
@ai_thread&.join(5)
|
|
108
216
|
@server&.shutdown
|
|
109
217
|
end
|
|
110
218
|
end
|