tina4ruby 3.13.35 → 3.13.36
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/dev_admin.rb +129 -15
- data/lib/tina4/public/js/tina4-dev-admin.js +437 -759
- data/lib/tina4/public/js/tina4-dev-admin.min.js +437 -759
- data/lib/tina4/rack_app.rb +105 -1
- data/lib/tina4/router.rb +43 -9
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/websocket.rb +48 -0
- data/lib/tina4.rb +1 -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: 7266dfb9cb9605c62b61d64de0c2586ff6824f2a22ad87aae74403f1d171c495
|
|
4
|
+
data.tar.gz: 0f9dffee4f4242c9c03899b79742a314bde065308da3e852c1af76fb1ade6be9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad75e74701786bf08fd4fc3c143a0d5f6731dbe0aa5feb5d46afb5c2472a113daa9dd0d2962f616c0a811318fed23091e40248117aee22923ea7fa35a81b6ab5
|
|
7
|
+
data.tar.gz: c5622aff3bf9fb522969dbe5431ae1a2aa8416712ade87ccd277529418a1aea6a427a25a211198342cfca0bebcff81305db70e6be01a0a11cfd7a2e99ebd6a9a
|
data/lib/tina4/dev_admin.rb
CHANGED
|
@@ -392,12 +392,30 @@ module Tina4
|
|
|
392
392
|
reload_type = body["type"] || "reload"
|
|
393
393
|
Tina4::Log.info("External reload trigger: #{reload_type}#{@reload_file.empty? ? '' : " (#{@reload_file})"}")
|
|
394
394
|
# Re-discover so files dropped into src/routes/ register without
|
|
395
|
-
# a server restart. Idempotent — already-loaded files are skipped
|
|
395
|
+
# a server restart. Idempotent — already-loaded files are skipped,
|
|
396
|
+
# changed files are re-loaded (mtime-tracked).
|
|
396
397
|
begin
|
|
397
398
|
Tina4::Router.rescan_routes!
|
|
398
399
|
rescue StandardError => e
|
|
399
400
|
Tina4::Log.error("Re-discover on reload failed: #{e.message}")
|
|
400
401
|
end
|
|
402
|
+
# WebSocket-primary reload: push an instant message to every browser
|
|
403
|
+
# connected on /__dev_reload. The toolbar client (and the dev-admin
|
|
404
|
+
# dashboard) act on this immediately — the mtime poll above is only a
|
|
405
|
+
# fallback for when the socket is down. CSS changes swap stylesheets;
|
|
406
|
+
# everything else triggers a full page reload. We normalise the wire
|
|
407
|
+
# `type` to "css"/"reload" (the clients only react to css/reload/change)
|
|
408
|
+
# but still echo the caller's original type in the HTTP response.
|
|
409
|
+
# Wrapped so a broadcast failure — or zero connected clients — never
|
|
410
|
+
# 500s the reload endpoint.
|
|
411
|
+
begin
|
|
412
|
+
ws_type = reload_type == "css" ? "css" : "reload"
|
|
413
|
+
Tina4::DevReload.broadcast(
|
|
414
|
+
JSON.generate({ type: ws_type, file: @reload_file, mtime: @reload_mtime })
|
|
415
|
+
)
|
|
416
|
+
rescue StandardError => e
|
|
417
|
+
Tina4::Log.error("Dev-reload WebSocket broadcast failed: #{e.message}")
|
|
418
|
+
end
|
|
401
419
|
json_response({ ok: true, type: reload_type })
|
|
402
420
|
when ["GET", "/__dev/api/status"]
|
|
403
421
|
json_response(status_payload)
|
|
@@ -1258,24 +1276,120 @@ module Tina4
|
|
|
1258
1276
|
resolved
|
|
1259
1277
|
end
|
|
1260
1278
|
|
|
1279
|
+
# Noise dirs + hidden dot-entries are hidden from the file browser,
|
|
1280
|
+
# except the env files — verbatim parity with PHP/Python dev-admin.
|
|
1281
|
+
DEV_FILES_IGNORED = %w[__pycache__ node_modules vendor .git venv .venv dist target .tina4].freeze
|
|
1282
|
+
|
|
1283
|
+
def dev_files_hidden?(name)
|
|
1284
|
+
return true if DEV_FILES_IGNORED.include?(name)
|
|
1285
|
+
name.start_with?(".") && name != ".env" && name != ".env.example"
|
|
1286
|
+
end
|
|
1287
|
+
|
|
1288
|
+
# Same 4-status mapping Python/PHP use for a porcelain code.
|
|
1289
|
+
def dev_git_status_label(code)
|
|
1290
|
+
return "untracked" if code == "??"
|
|
1291
|
+
return "modified" if code.include?("M")
|
|
1292
|
+
return "added" if code.include?("A")
|
|
1293
|
+
return "deleted" if code.include?("D")
|
|
1294
|
+
"clean"
|
|
1295
|
+
end
|
|
1296
|
+
|
|
1297
|
+
# Branch + porcelain status map for the file browser (mirrors PHP/Python
|
|
1298
|
+
# dev-admin). Paths git reports are relative to the repo root (always
|
|
1299
|
+
# forward-slash); the project root may sit inside a larger repo, so the
|
|
1300
|
+
# toplevel is returned too for rebasing. Empty on any error / no git.
|
|
1301
|
+
def dev_git_info(root)
|
|
1302
|
+
require "shellwords" unless defined?(Shellwords)
|
|
1303
|
+
esc = Shellwords.escape(root)
|
|
1304
|
+
inside = `cd #{esc} && git rev-parse --is-inside-work-tree 2>/dev/null`.strip
|
|
1305
|
+
return { branch: "", git_root: nil, status: {} } unless inside == "true"
|
|
1306
|
+
|
|
1307
|
+
branch = `cd #{esc} && git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
|
|
1308
|
+
top = `cd #{esc} && git rev-parse --show-toplevel 2>/dev/null`.strip
|
|
1309
|
+
git_root = top.empty? ? nil : top.tr("\\", "/").sub(%r{/+\z}, "")
|
|
1310
|
+
|
|
1311
|
+
status = {}
|
|
1312
|
+
`cd #{esc} && git status --porcelain -uall 2>/dev/null`.each_line do |line|
|
|
1313
|
+
line = line.chomp
|
|
1314
|
+
next if line.length < 4
|
|
1315
|
+
code = line[0, 2].strip
|
|
1316
|
+
path = line[3..].to_s.strip
|
|
1317
|
+
if (idx = path.index(" -> ")) # rename/copy — keep destination
|
|
1318
|
+
path = path[(idx + 4)..]
|
|
1319
|
+
end
|
|
1320
|
+
next if path.nil? || path.empty?
|
|
1321
|
+
status[path] = code
|
|
1322
|
+
end
|
|
1323
|
+
{ branch: branch, git_root: git_root, status: status }
|
|
1324
|
+
rescue StandardError
|
|
1325
|
+
{ branch: "", git_root: nil, status: {} }
|
|
1326
|
+
end
|
|
1327
|
+
|
|
1261
1328
|
def files_list(env)
|
|
1329
|
+
# Response shape matches tina4-python / tina4-php 1:1 so the dev-admin
|
|
1330
|
+
# SPA works against every framework: each entry carries `is_dir`,
|
|
1331
|
+
# `has_children`, `git_status` and `size`; the payload carries `branch`.
|
|
1262
1332
|
rel = query_param(env, "path") || "."
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1333
|
+
root = File.expand_path(Dir.pwd)
|
|
1334
|
+
git = dev_git_info(root)
|
|
1335
|
+
|
|
1336
|
+
# Missing/invalid paths return an empty-but-valid shape (not an error
|
|
1337
|
+
# body): the SPA restores expanded-folder state from localStorage and
|
|
1338
|
+
# non-existent folders would otherwise spam the console.
|
|
1339
|
+
target = begin
|
|
1340
|
+
safe_project_path(rel)
|
|
1341
|
+
rescue StandardError
|
|
1342
|
+
nil
|
|
1343
|
+
end
|
|
1344
|
+
unless target && File.directory?(target)
|
|
1345
|
+
return { path: rel, branch: git[:branch], entries: [], error: "not a directory" }
|
|
1346
|
+
end
|
|
1347
|
+
|
|
1348
|
+
root_fwd = root.tr("\\", "/")
|
|
1349
|
+
cwd_in_git = ""
|
|
1350
|
+
if git[:git_root] && git[:git_root] != root_fwd && root_fwd.start_with?(git[:git_root])
|
|
1351
|
+
cwd_in_git = root_fwd[git[:git_root].length..].to_s.sub(%r{\A/+}, "")
|
|
1352
|
+
cwd_in_git += "/" unless cwd_in_git.empty?
|
|
1353
|
+
end
|
|
1354
|
+
|
|
1355
|
+
entries = Dir.children(target).sort.filter_map do |name|
|
|
1356
|
+
next if dev_files_hidden?(name)
|
|
1357
|
+
full = File.join(target, name)
|
|
1358
|
+
entry_rel = full[root.length..].to_s.sub(%r{\A/+}, "").tr("\\", "/")
|
|
1359
|
+
is_dir = File.directory?(full)
|
|
1360
|
+
|
|
1361
|
+
# git status for this entry (same mapping PHP/Python use)
|
|
1362
|
+
git_path = cwd_in_git + entry_rel
|
|
1363
|
+
label = "clean"
|
|
1364
|
+
if (code = git[:status][git_path])
|
|
1365
|
+
label = dev_git_status_label(code)
|
|
1366
|
+
elsif is_dir
|
|
1367
|
+
prefix = "#{git_path}/" # propagate dirty status from any child
|
|
1368
|
+
hit = git[:status].find { |gf, _| gf.start_with?(prefix) }
|
|
1369
|
+
label = (hit && hit[1] == "??") ? "untracked" : "modified" if hit
|
|
1274
1370
|
end
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1371
|
+
|
|
1372
|
+
# has_children: does the dir contain anything visible?
|
|
1373
|
+
has_children = nil
|
|
1374
|
+
if is_dir
|
|
1375
|
+
has_children = begin
|
|
1376
|
+
Dir.children(full).any? { |c| !dev_files_hidden?(c) }
|
|
1377
|
+
rescue StandardError
|
|
1378
|
+
false
|
|
1379
|
+
end
|
|
1380
|
+
end
|
|
1381
|
+
|
|
1382
|
+
{
|
|
1383
|
+
name: name,
|
|
1384
|
+
path: entry_rel,
|
|
1385
|
+
is_dir: is_dir,
|
|
1386
|
+
has_children: has_children,
|
|
1387
|
+
git_status: label,
|
|
1388
|
+
size: is_dir ? nil : (File.size(full) rescue 0)
|
|
1389
|
+
}
|
|
1278
1390
|
end
|
|
1391
|
+
|
|
1392
|
+
{ path: rel, branch: git[:branch], entries: entries, count: entries.size }
|
|
1279
1393
|
end
|
|
1280
1394
|
|
|
1281
1395
|
def file_read_payload(rel)
|