tina4ruby 3.13.81 → 3.13.82
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/CHANGELOG.md +35 -2
- data/README.md +4 -4
- data/lib/tina4/background.rb +21 -2
- data/lib/tina4/cli.rb +141 -14
- data/lib/tina4/mqtt.rb +800 -0
- data/lib/tina4/mqtt_message.rb +78 -0
- data/lib/tina4/public/css/tina4.css +56 -130
- data/lib/tina4/public/css/tina4.min.css +1 -1
- data/lib/tina4/rack_app.rb +13 -0
- data/lib/tina4/router.rb +13 -2
- data/lib/tina4/scss/tina4css/_pagination.scss +63 -0
- data/lib/tina4/scss/tina4css/_utilities.scss +11 -0
- data/lib/tina4/scss/tina4css/tina4.scss +1 -0
- data/lib/tina4/scss_compiler.rb +48 -3
- data/lib/tina4/test_client.rb +39 -52
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4.rb +16 -1
- metadata +6 -3
- /data/lib/tina4/{sql_translation.rb → sql_translator.rb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c8f9a8a50b4bb94a8b19b81774ce30e7a9d64fca932c2eb6a49016dc0352171
|
|
4
|
+
data.tar.gz: 90066d82b0085d019c3118fe57d225eb78206fe44f55c235dd3e7d67d8451382
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 042b4dbfa4faf371dde9eb25e3e4406170c46086c29f1ff67afecad0fb61c482605f4f912469251b8640545142eeb295dd7eb1797081137575d941ebccf3f9be
|
|
7
|
+
data.tar.gz: e5e303934c51d2fc83692817eef9f26382d5bbd5c1b735f6a0b3bcd65a13a7a902574b070cd86a1b60cf0e17a34788444507c5b8cd62c9d8a60aea626abff80f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
Tina4 keeps ONE version across all four frameworks (Python, PHP, Ruby, Node.js), so a version
|
|
4
|
+
number means the same thing everywhere.
|
|
5
|
+
|
|
6
|
+
**The authoritative release notes for every shipped version live in the documentation:**
|
|
7
|
+
https://tina4.com/ruby/36-releases
|
|
8
|
+
|
|
9
|
+
This file is deliberately NOT a copy of those notes. Duplicating them is exactly how a
|
|
10
|
+
changelog rots into claiming a version that was never cut, so this file records only
|
|
11
|
+
UNRELEASED work. When a version ships, its notes go to the release notes above.
|
|
12
|
+
|
|
13
|
+
## Unreleased
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **MQTT 3.1.1 client** (`Tina4::Mqtt` / `Tina4::MqttMessage`), zero-dependency (stdlib `socket` +
|
|
18
|
+
lazily-required `openssl`), verified against a real broker with no mocks. publish/subscribe/consume,
|
|
19
|
+
QoS 0/1, retained, Last Will, per-client TLS, QoS 2 refused loudly. Ruby ships all 97 shared
|
|
20
|
+
features plus its native ERB engine, for **98 built-in features**.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Internal: the SQL dialect-translation file is renamed
|
|
25
|
+
`lib/tina4/sql_translation.rb` -> `lib/tina4/sql_translator.rb`, so the filename matches the
|
|
26
|
+
`Tina4::SQLTranslator` class it defines (and the sibling frameworks). The class name, its
|
|
27
|
+
methods and its behaviour are unchanged, and `require "tina4"` is unaffected - this is a
|
|
28
|
+
filename alignment, not an API change. Only code that bypassed the gem's own entry point with
|
|
29
|
+
a direct `require "tina4/sql_translation"` needs to update the path.
|
|
30
|
+
|
|
31
|
+
## Earlier history (pre-3.x)
|
|
32
|
+
|
|
33
|
+
Kept for reference only. The versions below are from the 0.x line, long before the unified
|
|
34
|
+
3.x versioning; everything from 3.x onward is in the release notes linked above.
|
|
35
|
+
|
|
3
36
|
## [0.4.0] - 2026-03-18
|
|
4
37
|
|
|
5
38
|
### Added
|
|
@@ -14,7 +47,7 @@
|
|
|
14
47
|
- Recursive descent GraphQL parser (queries, mutations, fragments, variables, aliases)
|
|
15
48
|
- Depth-first AST executor with resolver pattern
|
|
16
49
|
- GraphQL schema with programmatic type registration
|
|
17
|
-
- ORM auto-schema generation (`schema.from_orm(User)`)
|
|
50
|
+
- ORM auto-schema generation (`schema.from_orm(User)`) - auto-creates CRUD queries/mutations
|
|
18
51
|
- GraphiQL UI served at GET /graphql
|
|
19
52
|
- Route integration via `gql.register_route("/graphql")`
|
|
20
53
|
- Full GraphQL type system (scalars, objects, lists, non-null, input objects)
|
|
@@ -23,7 +56,7 @@
|
|
|
23
56
|
|
|
24
57
|
### Added
|
|
25
58
|
- Default auth protection for POST/PUT/PATCH/DELETE routes (matching tina4_python behavior)
|
|
26
|
-
- API_KEY bypass in bearer auth
|
|
59
|
+
- API_KEY bypass in bearer auth - if `ENV["API_KEY"]` matches the bearer token, access is granted
|
|
27
60
|
- `auth: false` option to make write routes public (equivalent to tina4_python's `@noauth()`)
|
|
28
61
|
- `default_secure_auth` cached auth handler for performance
|
|
29
62
|
- `resolve_auth` helper for flexible auth resolution
|
data/README.md
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
<h1 align="center">Tina4 Ruby</h1>
|
|
5
5
|
<h3 align="center">TINA4: The Intelligent Native Application 4ramework</h3>
|
|
6
6
|
<p align="center"><em>Simple. Fast. Human. | Built for AI. Built for you.</em></p>
|
|
7
|
-
<p align="center">
|
|
7
|
+
<p align="center">98 built-in features. Zero runtime dependencies. One require, everything works.</p>
|
|
8
8
|
<p align="center">
|
|
9
9
|
<a href="https://rubygems.org/gems/tina4ruby"><img src="https://img.shields.io/gem/v/tina4ruby?color=7b1fa2&label=RubyGems" alt="RubyGems"></a>
|
|
10
10
|
<img src="https://img.shields.io/badge/tests-2%2C508%20passing-brightgreen" alt="Tests">
|
|
11
|
-
<img src="https://img.shields.io/badge/features-
|
|
11
|
+
<img src="https://img.shields.io/badge/features-98-blue" alt="Features">
|
|
12
12
|
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero Deps">
|
|
13
13
|
<a href="https://tina4.com"><img src="https://img.shields.io/badge/docs-tina4.com-7b1fa2" alt="Docs"></a>
|
|
14
14
|
</p>
|
|
@@ -91,7 +91,7 @@ Benchmarked with `wrk`: 5,000 requests, 50 concurrent, median of 3 runs:
|
|
|
91
91
|
| **Tina4 Ruby** | **10,243** | 0 | 55 |
|
|
92
92
|
| Sinatra | 9,548 | 5+ | ~4 |
|
|
93
93
|
|
|
94
|
-
Tina4 Ruby outperforms Sinatra while delivering **
|
|
94
|
+
Tina4 Ruby outperforms Sinatra while delivering **98 features vs ~4**, with zero runtime dependencies.
|
|
95
95
|
|
|
96
96
|
**Across all 4 Tina4 implementations:**
|
|
97
97
|
|
|
@@ -105,7 +105,7 @@ Tina4 Ruby outperforms Sinatra while delivering **55 features vs ~4**, with zero
|
|
|
105
105
|
|
|
106
106
|
## Cross-Framework Parity
|
|
107
107
|
|
|
108
|
-
Tina4 ships identical features across four languages: same architecture, same conventions, same
|
|
108
|
+
Tina4 ships identical features across four languages: same architecture, same conventions, same 97 features:
|
|
109
109
|
|
|
110
110
|
| | Python | PHP | Ruby | Node.js |
|
|
111
111
|
|---|--------|-----|------|---------|
|
data/lib/tina4/background.rb
CHANGED
|
@@ -36,15 +36,34 @@ module Tina4
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# Stop and join every running task. Called on graceful shutdown.
|
|
39
|
+
#
|
|
40
|
+
# Each stop_task deregisters its own descriptor, so there is no blanket
|
|
41
|
+
# `tasks.clear` here: clearing would ALSO drop a task registered while
|
|
42
|
+
# this loop was running — leaving its thread alive but invisible in the
|
|
43
|
+
# registry, which is the worse of the two failure modes.
|
|
39
44
|
def stop_all(timeout: 2.0)
|
|
40
45
|
snapshot = mutex.synchronize { tasks.dup }
|
|
41
46
|
snapshot.each { |task| stop_task(task, timeout: timeout) }
|
|
42
|
-
mutex.synchronize { tasks.clear }
|
|
43
47
|
end
|
|
44
48
|
|
|
45
|
-
# Stop a single task. Used by tests that register, fire,
|
|
49
|
+
# Stop a single task and DEREGISTER it. Used by tests that register, fire,
|
|
50
|
+
# then stop, and by any subsystem that owns a task for part of its life
|
|
51
|
+
# (e.g. Mqtt::Client#stop_keepalive).
|
|
52
|
+
#
|
|
53
|
+
# The descriptor is removed from `tasks` so the registry never reports a
|
|
54
|
+
# stopped task as registered — leaving it in place made `tasks` grow for
|
|
55
|
+
# the life of the process on every start/stop cycle and made introspection
|
|
56
|
+
# lie about what is actually running.
|
|
57
|
+
#
|
|
58
|
+
# Idempotent: a second call on the same descriptor removes nothing, finds
|
|
59
|
+
# no thread and returns safely.
|
|
46
60
|
def stop_task(task, timeout: 2.0)
|
|
47
61
|
task[:running] = false
|
|
62
|
+
# Identity, not equality: `tasks.delete(task)` uses `==`, which would
|
|
63
|
+
# take out any OTHER descriptor that happens to hold an equal Hash
|
|
64
|
+
# (same callback, same interval). Only this exact descriptor goes.
|
|
65
|
+
mutex.synchronize { tasks.delete_if { |registered| registered.equal?(task) } }
|
|
66
|
+
|
|
48
67
|
thread = task[:thread]
|
|
49
68
|
return unless thread
|
|
50
69
|
|
data/lib/tina4/cli.rb
CHANGED
|
@@ -75,6 +75,58 @@ module Tina4
|
|
|
75
75
|
"help" => { handler: :cmd_help, summary: "Show this help message" },
|
|
76
76
|
}.freeze
|
|
77
77
|
|
|
78
|
+
# ── Delegation to the `tina4` client ────────────────────────────────
|
|
79
|
+
#
|
|
80
|
+
# `doctor`, `setup` and `deploy` are owned by the Rust `tina4` client, not by
|
|
81
|
+
# any framework. `doctor` probes ALL FOUR runtimes plus package managers,
|
|
82
|
+
# ports and global AI-skills currency; `setup` installs language runtimes
|
|
83
|
+
# (Homebrew / Chocolatey, with UAC elevation on Windows) and scaffolds a
|
|
84
|
+
# project from nothing; `deploy` writes deployment boilerplate baked into the
|
|
85
|
+
# client binary. Cloning any of them into four languages would duplicate
|
|
86
|
+
# hundreds of lines per language for zero new capability — and four copies
|
|
87
|
+
# would immediately drift.
|
|
88
|
+
#
|
|
89
|
+
# So the framework CLI DELEGATES: it resolves `tina4` on PATH, runs it with
|
|
90
|
+
# the same argv, and exits with the client's exit code. All four frameworks
|
|
91
|
+
# reach the SAME implementation, which is a stronger parity guarantee than
|
|
92
|
+
# four ports.
|
|
93
|
+
#
|
|
94
|
+
# Delegation is ALLOW-LISTED, never blind. The client forwards ITS unknown
|
|
95
|
+
# commands to the framework CLI, so a framework that forwarded its unknowns
|
|
96
|
+
# back would ping-pong an unknown command between two processes forever. This
|
|
97
|
+
# closed set contains only commands the client dispatches natively, so no loop
|
|
98
|
+
# is possible by construction, and a real typo still gets "Unknown command".
|
|
99
|
+
#
|
|
100
|
+
# There are no handlers here: #run runs `tina4 <name> <args...>` and exits
|
|
101
|
+
# with its code. Keep this set closed and identical in all four frameworks.
|
|
102
|
+
# Summaries are the client's own wording, verbatim. Ruby mirror of the Python
|
|
103
|
+
# master's DELEGATED / _delegate_to_client.
|
|
104
|
+
|
|
105
|
+
DELEGATED = {
|
|
106
|
+
"doctor" => { summary: "Check installed languages and tools" },
|
|
107
|
+
"setup" => { summary: "Guided, menu-driven setup: install everything + scaffold a ready-to-run project" },
|
|
108
|
+
"deploy" => { usage: "<docker|systemd|nginx|cpanel> [--force]", args: ["target"],
|
|
109
|
+
summary: "Generate deployment scaffolding (Dockerfile, systemd unit, nginx block, cPanel)" },
|
|
110
|
+
}.freeze
|
|
111
|
+
|
|
112
|
+
CLIENT_BINARY = "tina4"
|
|
113
|
+
|
|
114
|
+
# Internal process marker (same class as the client's own
|
|
115
|
+
# TINA4_SETUP_ELEVATED): set on the child so a client that resolves back to a
|
|
116
|
+
# framework CLI is caught instead of spawning forever. NOT user configuration
|
|
117
|
+
# — deliberately absent from the CLI's known_vars().
|
|
118
|
+
DELEGATION_GUARD_ENV = "TINA4_CLI_DELEGATED"
|
|
119
|
+
|
|
120
|
+
# 127 is the conventional "command not found" and covers both ways the client
|
|
121
|
+
# can be unreachable (absent from PATH, or the loop guard tripping).
|
|
122
|
+
EXIT_CLIENT_UNAVAILABLE = 127
|
|
123
|
+
EXIT_UNKNOWN_COMMAND = 1
|
|
124
|
+
|
|
125
|
+
CLIENT_INSTALL_HINT = <<~HINT.freeze
|
|
126
|
+
Install it: curl -fsSL https://tina4.com/install.sh | sh
|
|
127
|
+
Windows: irm https://tina4.com/install.ps1 | iex
|
|
128
|
+
HINT
|
|
129
|
+
|
|
78
130
|
# ── Field type mapping ──────────────────────────────────────────────
|
|
79
131
|
FIELD_TYPE_MAP = {
|
|
80
132
|
"string" => { orm: "string_field", sql: "VARCHAR(255)", default: "''" },
|
|
@@ -105,15 +157,68 @@ module Tina4
|
|
|
105
157
|
spec = COMMANDS[command]
|
|
106
158
|
if spec
|
|
107
159
|
send(spec[:handler], argv)
|
|
160
|
+
elsif DELEGATED.key?(command)
|
|
161
|
+
exit delegate_to_client(command, argv)
|
|
108
162
|
else
|
|
163
|
+
# A genuinely unknown command is an ERROR: exit non-zero so a typo in a
|
|
164
|
+
# script or CI step fails loudly instead of reporting success.
|
|
109
165
|
puts "Unknown command: #{command}"
|
|
110
166
|
cmd_help
|
|
111
|
-
exit
|
|
167
|
+
exit EXIT_UNKNOWN_COMMAND
|
|
112
168
|
end
|
|
113
169
|
end
|
|
114
170
|
|
|
115
171
|
private
|
|
116
172
|
|
|
173
|
+
# Absolute path of the `tina4` client on PATH, or nil if it isn't there.
|
|
174
|
+
#
|
|
175
|
+
# Scans PATH directly rather than shelling out to which/where — one less
|
|
176
|
+
# process and it behaves the same on every platform.
|
|
177
|
+
def find_client
|
|
178
|
+
windows = RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
179
|
+
names = windows ? %W[#{CLIENT_BINARY}.exe #{CLIENT_BINARY}.cmd #{CLIENT_BINARY}.bat] : [CLIENT_BINARY]
|
|
180
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |dir|
|
|
181
|
+
next if dir.empty?
|
|
182
|
+
|
|
183
|
+
names.each do |name|
|
|
184
|
+
candidate = File.join(dir, name)
|
|
185
|
+
return candidate if File.file?(candidate) && (windows || File.executable?(candidate))
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
nil
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Run `tina4 <command> <args...>`, returning the client's exit code.
|
|
192
|
+
#
|
|
193
|
+
# Returns EXIT_CLIENT_UNAVAILABLE (127) with an actionable message when the
|
|
194
|
+
# client is not on PATH, or when the re-entry guard shows the resolved `tina4`
|
|
195
|
+
# came back to a framework CLI (a delegation loop).
|
|
196
|
+
def delegate_to_client(command, args)
|
|
197
|
+
if ENV[DELEGATION_GUARD_ENV] == command
|
|
198
|
+
warn " Refusing to delegate '#{command}' again — the 'tina4' on your PATH"
|
|
199
|
+
warn " resolved back to a framework CLI instead of the tina4 client."
|
|
200
|
+
warn ""
|
|
201
|
+
warn " Check which 'tina4' comes first on your PATH and put the client first."
|
|
202
|
+
return EXIT_CLIENT_UNAVAILABLE
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
client = find_client
|
|
206
|
+
if client.nil?
|
|
207
|
+
warn " '#{command}' is provided by the tina4 client, which is not on your PATH."
|
|
208
|
+
warn ""
|
|
209
|
+
CLIENT_INSTALL_HINT.each_line { |line| warn " #{line.chomp}" }
|
|
210
|
+
warn ""
|
|
211
|
+
warn " Then run: #{CLIENT_BINARY} #{command}"
|
|
212
|
+
return EXIT_CLIENT_UNAVAILABLE
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Array form => no shell, so no quoting/injection surface. stdio is
|
|
216
|
+
# inherited, so the client's interactive prompts (setup) and colour output
|
|
217
|
+
# work exactly as if it had been invoked directly.
|
|
218
|
+
system({ DELEGATION_GUARD_ENV => command }, client, command, *args)
|
|
219
|
+
$?&.exitstatus || EXIT_CLIENT_UNAVAILABLE
|
|
220
|
+
end
|
|
221
|
+
|
|
117
222
|
# ── Helpers ──────────────────────────────────────────────────────────
|
|
118
223
|
|
|
119
224
|
# CamelCase -> snake_case: ProductCategory -> product_category
|
|
@@ -2083,13 +2188,12 @@ module Tina4
|
|
|
2083
2188
|
content = <<~RUBY
|
|
2084
2189
|
# #{name} request validator.
|
|
2085
2190
|
#
|
|
2086
|
-
# Tina4::Validator
|
|
2087
|
-
#
|
|
2088
|
-
#
|
|
2191
|
+
# Tina4::Validator comes with `require "tina4"` — no extra require needed.
|
|
2192
|
+
# This FILE is not auto-discovered though (only src/routes/ and src/orm/
|
|
2193
|
+
# are), so require it from the route that validates:
|
|
2089
2194
|
# require_relative "../validators/#{snake}"
|
|
2090
2195
|
# v = validate_#{snake}(request.body)
|
|
2091
2196
|
# next response.json({ error: v.errors.first[:message] }, 400) unless v.is_valid?
|
|
2092
|
-
require "tina4/validator"
|
|
2093
2197
|
|
|
2094
2198
|
def validate_#{snake}(data)
|
|
2095
2199
|
validator = Tina4::Validator.new(data)
|
|
@@ -2756,20 +2860,23 @@ module Tina4
|
|
|
2756
2860
|
|
|
2757
2861
|
# Print the human-readable command reference.
|
|
2758
2862
|
#
|
|
2759
|
-
# Generated from the COMMANDS and GENERATORS registries — the SAME
|
|
2760
|
-
# source of truth that drives dispatch (#run / #cmd_generate) and the
|
|
2863
|
+
# Generated from the COMMANDS, DELEGATED and GENERATORS registries — the SAME
|
|
2864
|
+
# single source of truth that drives dispatch (#run / #cmd_generate) and the
|
|
2761
2865
|
# `commands --json` manifest — so the help text can never drift from what
|
|
2762
2866
|
# the CLI actually does.
|
|
2763
2867
|
def cmd_help(_argv = nil)
|
|
2764
2868
|
command_rows = COMMANDS.map do |name, spec|
|
|
2765
2869
|
["#{name} #{spec[:usage]}".rstrip, spec[:summary]]
|
|
2766
2870
|
end
|
|
2871
|
+
delegated_rows = DELEGATED.map do |name, spec|
|
|
2872
|
+
["#{name} #{spec[:usage]}".rstrip, spec[:summary]]
|
|
2873
|
+
end
|
|
2767
2874
|
generator_rows = GENERATORS.map do |name, spec|
|
|
2768
2875
|
["generate #{name} #{spec[:usage]}".rstrip, spec[:summary]]
|
|
2769
2876
|
end
|
|
2770
2877
|
# Align summaries in a column; a left cell longer than the cap overflows
|
|
2771
2878
|
# cleanly (2-space gap) rather than pushing every other summary out.
|
|
2772
|
-
pad = [46, (command_rows + generator_rows).map { |left, _| left.length }.max].min
|
|
2879
|
+
pad = [46, (command_rows + delegated_rows + generator_rows).map { |left, _| left.length }.max].min
|
|
2773
2880
|
|
|
2774
2881
|
row = lambda do |left, summary|
|
|
2775
2882
|
gap = left.length <= pad ? pad : left.length
|
|
@@ -2778,6 +2885,10 @@ module Tina4
|
|
|
2778
2885
|
|
|
2779
2886
|
lines = ["Tina4 Ruby CLI", "", "Usage: tina4ruby COMMAND [options]", "", "Commands:"]
|
|
2780
2887
|
lines += command_rows.map { |left, summary| row.call(left, summary) }
|
|
2888
|
+
lines += ["", "Delegated to the #{CLIENT_BINARY} client (same behaviour in every framework):"]
|
|
2889
|
+
lines += delegated_rows.map { |left, summary| row.call(left, summary) }
|
|
2890
|
+
lines += [" (these run the #{CLIENT_BINARY} client — install: " \
|
|
2891
|
+
"curl -fsSL https://tina4.com/install.sh | sh)"]
|
|
2781
2892
|
lines += ["", "Generators:"]
|
|
2782
2893
|
lines += generator_rows.map { |left, summary| row.call(left, summary) }
|
|
2783
2894
|
lines += [
|
|
@@ -2809,14 +2920,19 @@ module Tina4
|
|
|
2809
2920
|
|
|
2810
2921
|
# Build the machine-readable manifest of the CLI's command surface.
|
|
2811
2922
|
#
|
|
2812
|
-
# Pure data: reads the COMMANDS
|
|
2813
|
-
# bootstrap, no database, no migrations, no app imports. This is
|
|
2814
|
-
# what `commands --json` serializes and what the tina4 client consumes
|
|
2815
|
-
# discover which commands this framework supports.
|
|
2923
|
+
# Pure data: reads the COMMANDS and DELEGATED registries and the framework
|
|
2924
|
+
# version — no bootstrap, no database, no migrations, no app imports. This is
|
|
2925
|
+
# exactly what `commands --json` serializes and what the tina4 client consumes
|
|
2926
|
+
# to discover which commands this framework supports.
|
|
2927
|
+
#
|
|
2928
|
+
# Commands handed to the `tina4` client carry "delegated" => true, so the
|
|
2929
|
+
# manifest describes the WHOLE surface the CLI accepts while still saying who
|
|
2930
|
+
# implements each one. The client needs no change: its help renderer already
|
|
2931
|
+
# drops manifest names that clash with its own natives.
|
|
2816
2932
|
#
|
|
2817
2933
|
# Shape:
|
|
2818
2934
|
# { "framework" => "ruby", "version" => "<x.y.z>",
|
|
2819
|
-
# "commands" => [ { "name", "summary", "args"?, "subcommands"? }, ... ] }
|
|
2935
|
+
# "commands" => [ { "name", "summary", "args"?, "subcommands"?, "delegated"? }, ... ] }
|
|
2820
2936
|
def commands_manifest
|
|
2821
2937
|
require_relative "version"
|
|
2822
2938
|
commands = COMMANDS.map do |name, spec|
|
|
@@ -2825,6 +2941,11 @@ module Tina4
|
|
|
2825
2941
|
entry["subcommands"] = spec[:subcommands].dup if spec[:subcommands]
|
|
2826
2942
|
entry
|
|
2827
2943
|
end
|
|
2944
|
+
commands += DELEGATED.map do |name, spec|
|
|
2945
|
+
entry = { "name" => name, "summary" => spec[:summary], "delegated" => true }
|
|
2946
|
+
entry["args"] = spec[:args].dup if spec[:args]
|
|
2947
|
+
entry
|
|
2948
|
+
end
|
|
2828
2949
|
{ "framework" => "ruby", "version" => Tina4::VERSION, "commands" => commands }
|
|
2829
2950
|
end
|
|
2830
2951
|
|
|
@@ -2851,7 +2972,8 @@ module Tina4
|
|
|
2851
2972
|
puts "\nTina4 #{manifest['framework']} - #{manifest['version']}\n\n"
|
|
2852
2973
|
width = manifest["commands"].map { |command| command["name"].length }.max
|
|
2853
2974
|
manifest["commands"].each do |command|
|
|
2854
|
-
|
|
2975
|
+
marker = command["delegated"] ? " (#{CLIENT_BINARY} client)" : ""
|
|
2976
|
+
puts " #{command['name'].ljust(width)} #{command['summary']}#{marker}"
|
|
2855
2977
|
if command["subcommands"]
|
|
2856
2978
|
puts " #{''.ljust(width)} #{command['subcommands'].join(', ')}"
|
|
2857
2979
|
end
|
|
@@ -3052,6 +3174,11 @@ module Tina4
|
|
|
3052
3174
|
{% block content %}{% endblock %}
|
|
3053
3175
|
<script src="/js/tina4.min.js"></script>
|
|
3054
3176
|
<script src="/js/frond.min.js"></script>
|
|
3177
|
+
<!-- tina4-js reactive frontend (signals, components, router).
|
|
3178
|
+
Served from the framework's bundled public dir, same as
|
|
3179
|
+
tina4-python/example/src/templates/base.twig:47-48 and
|
|
3180
|
+
tina4-php/example/src/templates/base.twig:47-48. -->
|
|
3181
|
+
<script src="/js/tina4js.min.js"></script>
|
|
3055
3182
|
{% block scripts %}{% endblock %}
|
|
3056
3183
|
</body>
|
|
3057
3184
|
</html>
|