tina4ruby 3.13.158 → 3.13.159
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 +15 -0
- data/lib/tina4/cli.rb +73 -18
- data/lib/tina4/migration.rb +35 -0
- data/lib/tina4/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c8eaa3629bc154d2dfc5419385857a0f9c3a8ede768af9363664c80448be9e19
|
|
4
|
+
data.tar.gz: aefe567a6fb3cf128750add20e11dc7683325a14b978708c57010ac5d4062bbe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2041d862f935b2a6611323856b8cfc7ed376009f1a0d7a52583f9ada47eeb4da1298e01c8ef6b48f17bbc71bd2d2c13bf4ae712d1004ba80636c772e18ab358f
|
|
7
|
+
data.tar.gz: 1d4d7726c126abab28b2f6261b1f5961249e8a2a8ca77e579941379c7943705d91094b0e2caf7701193a1f633ce7ccdd59a5287b1e1bc111ee438c70d3301cda
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ number means the same thing everywhere.
|
|
|
6
6
|
**The authoritative release notes for every shipped version live in the documentation:**
|
|
7
7
|
https://tina4.com/ruby/36-releases
|
|
8
8
|
|
|
9
|
+
## 3.13.159 - 2026-11-06
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `generate route/service/queue/websocket/listener` co-emitted a spec that asserted the
|
|
13
|
+
stub still raised `NotImplementedError`, so filling the handler in correctly turned the
|
|
14
|
+
project's own suite red. The generated spec now asserts the handler is wired and stays
|
|
15
|
+
green both as the scaffold stub and once implemented (#270).
|
|
16
|
+
- `tina4ruby migrate` applied a freshly scaffolded comment-only migration and recorded it
|
|
17
|
+
passed, so the real SQL written later under the same filename never ran. The runner now
|
|
18
|
+
keeps a migration pending while it still bears a `tina4:edit` marker and has no
|
|
19
|
+
executable SQL; a scaffolded `CREATE TABLE` is real DDL and still applies (#288).
|
|
20
|
+
- `migrate:create create_*_view` scaffolded a runnable `CREATE TABLE <name>_view`, so
|
|
21
|
+
`migrate` built a table and never the view. A `*_view` name now scaffolds commented
|
|
22
|
+
`CREATE VIEW` guidance that is kept pending until written (#297).
|
|
23
|
+
|
|
9
24
|
## 3.13.158 - 2026-11-05
|
|
10
25
|
|
|
11
26
|
### Added
|
data/lib/tina4/cli.rb
CHANGED
|
@@ -2446,10 +2446,23 @@ module Tina4
|
|
|
2446
2446
|
fields = fields_override&.any? ? fields_override : parse_fields(flags["fields"])
|
|
2447
2447
|
is_create = name.start_with?("create_") || !fields_override.nil?
|
|
2448
2448
|
|
|
2449
|
+
# A `create_*_view` name is a VIEW, not a table. Emitting a runnable
|
|
2450
|
+
# `CREATE TABLE <name>_view` made migrate build a table and never the
|
|
2451
|
+
# view, silently (#297). A view needs a SELECT, so scaffold it as
|
|
2452
|
+
# commented guidance with tina4:edit markers and no executable SQL, so
|
|
2453
|
+
# startup auto-migrate keeps it pending until it is written (#288).
|
|
2454
|
+
is_view = is_create && fields_override.nil? && table.end_with?("_view")
|
|
2455
|
+
|
|
2449
2456
|
filename = "#{timestamp}_#{name}.sql"
|
|
2450
2457
|
path = File.join(dir, filename)
|
|
2451
2458
|
|
|
2452
|
-
if
|
|
2459
|
+
if is_view
|
|
2460
|
+
up_sql = "-- tina4:edit write the CREATE VIEW statement, then remove the\n" \
|
|
2461
|
+
"-- tina4:edit lines. For example:\n" \
|
|
2462
|
+
"-- CREATE VIEW #{table} AS\n" \
|
|
2463
|
+
"-- SELECT id, /* columns */ FROM source_table WHERE /* ... */;"
|
|
2464
|
+
down_sql = "DROP VIEW IF EXISTS #{table};"
|
|
2465
|
+
elsif is_create
|
|
2453
2466
|
col_lines = [" id INTEGER PRIMARY KEY AUTOINCREMENT"]
|
|
2454
2467
|
fields.each do |fname, ftype|
|
|
2455
2468
|
info = FIELD_TYPE_MAP[ftype] || FIELD_TYPE_MAP["string"]
|
|
@@ -2500,9 +2513,10 @@ module Tina4
|
|
|
2500
2513
|
File.write(down_path, down_content)
|
|
2501
2514
|
puts " Created #{down_path}"
|
|
2502
2515
|
|
|
2503
|
-
# Co-emit a real apply-UP/DOWN spec — only for CREATE migrations,
|
|
2504
|
-
# SQL is real DDL to assert against (a placeholder ALTER
|
|
2505
|
-
|
|
2516
|
+
# Co-emit a real apply-UP/DOWN spec — only for CREATE TABLE migrations,
|
|
2517
|
+
# whose UP SQL is real DDL to assert against (a placeholder ALTER, or a
|
|
2518
|
+
# VIEW stub that still needs its SELECT, has nothing concrete yet).
|
|
2519
|
+
emit_migration_test(name, table, filename, down_filename) if emit_test && is_create && !is_view
|
|
2506
2520
|
end
|
|
2507
2521
|
|
|
2508
2522
|
# ── Generator: middleware ────────────────────────────────────────────
|
|
@@ -3492,10 +3506,11 @@ module Tina4
|
|
|
3492
3506
|
# Routing spec for #{route} — no mocks, real Router.
|
|
3493
3507
|
#
|
|
3494
3508
|
# Generated with src/routes/#{route}.rb by `tina4ruby generate route
|
|
3495
|
-
# #{route}` (no --model). The handlers
|
|
3496
|
-
# you implement them
|
|
3497
|
-
# routes register on the REAL Router, and the list handler
|
|
3498
|
-
#
|
|
3509
|
+
# #{route}` (no --model). The handlers start as AI-FILL stubs that raise
|
|
3510
|
+
# until you implement them. This tests what is live on generation: all
|
|
3511
|
+
# five routes register on the REAL Router, and the list handler is wired
|
|
3512
|
+
# and callable. It stays GREEN whether the stub still raises OR you have
|
|
3513
|
+
# filled it in — implementing the handler never turns this spec red.
|
|
3499
3514
|
require "spec_helper"
|
|
3500
3515
|
|
|
3501
3516
|
RSpec.describe "#{klass} routing (real Router)" do
|
|
@@ -3510,9 +3525,20 @@ module Tina4
|
|
|
3510
3525
|
expect(paths).to include(["DELETE", "/api/#{route}/{id:int}"])
|
|
3511
3526
|
end
|
|
3512
3527
|
|
|
3513
|
-
it "the list handler is a
|
|
3528
|
+
it "the list handler is wired (green as a stub AND when filled)" do
|
|
3529
|
+
# It starts as a loud AI-FILL stub (raises NotImplementedError); once
|
|
3530
|
+
# you implement it this spec STILL passes — it asserts wiring, never
|
|
3531
|
+
# that the stub is unfinished, so doing the work never turns red.
|
|
3514
3532
|
route, = Tina4::Router.match("GET", "/api/#{route}")
|
|
3515
|
-
expect
|
|
3533
|
+
expect(route.handler).to respond_to(:call)
|
|
3534
|
+
request = Tina4::Request.new("REQUEST_METHOD" => "GET",
|
|
3535
|
+
"PATH_INFO" => "/api/#{route}", "QUERY_STRING" => "")
|
|
3536
|
+
begin
|
|
3537
|
+
result = route.handler.call(request, Tina4::Response.new)
|
|
3538
|
+
rescue NotImplementedError
|
|
3539
|
+
next # still the scaffold stub — expected until filled in
|
|
3540
|
+
end
|
|
3541
|
+
expect(result).not_to be_nil # filled in: must hand back a response
|
|
3516
3542
|
end
|
|
3517
3543
|
end
|
|
3518
3544
|
RUBY
|
|
@@ -3581,8 +3607,15 @@ module Tina4
|
|
|
3581
3607
|
expect(Tina4::ServiceRunner.list.any? { |s| s[:name] == "#{snake}" }).to be true
|
|
3582
3608
|
end
|
|
3583
3609
|
|
|
3584
|
-
it "the task is a
|
|
3585
|
-
|
|
3610
|
+
it "the task is wired (green as a stub AND when filled)" do
|
|
3611
|
+
# Starts as a loud AI-FILL stub (raises NotImplementedError); once
|
|
3612
|
+
# implemented this spec STILL passes, so doing the work never turns red.
|
|
3613
|
+
expect(defined?(#{snake}_task)).to be_truthy
|
|
3614
|
+
begin
|
|
3615
|
+
#{snake}_task(nil)
|
|
3616
|
+
rescue NotImplementedError
|
|
3617
|
+
next # still the scaffold stub — expected until filled in
|
|
3618
|
+
end
|
|
3586
3619
|
end
|
|
3587
3620
|
end
|
|
3588
3621
|
RUBY
|
|
@@ -3619,8 +3652,15 @@ module Tina4
|
|
|
3619
3652
|
expect(svc[:options][:daemon]).to be true
|
|
3620
3653
|
end
|
|
3621
3654
|
|
|
3622
|
-
it "handle_#{slug} is a
|
|
3623
|
-
|
|
3655
|
+
it "handle_#{slug} is wired (green as a stub AND when filled)" do
|
|
3656
|
+
# Starts as a loud AI-FILL stub (raises NotImplementedError); once
|
|
3657
|
+
# implemented this spec STILL passes, so doing the work never turns red.
|
|
3658
|
+
expect(defined?(handle_#{slug})).to be_truthy
|
|
3659
|
+
begin
|
|
3660
|
+
handle_#{slug}({})
|
|
3661
|
+
rescue NotImplementedError
|
|
3662
|
+
next # still the scaffold stub — expected until filled in
|
|
3663
|
+
end
|
|
3624
3664
|
end
|
|
3625
3665
|
end
|
|
3626
3666
|
RUBY
|
|
@@ -3727,8 +3767,15 @@ module Tina4
|
|
|
3727
3767
|
expect(ws_route.handler.call(nil, :close, nil)).to be_nil
|
|
3728
3768
|
end
|
|
3729
3769
|
|
|
3730
|
-
it "the :message branch is a
|
|
3731
|
-
|
|
3770
|
+
it "the :message branch is wired (green as a stub AND when filled)" do
|
|
3771
|
+
# Starts as a loud AI-FILL stub (raises NotImplementedError); once
|
|
3772
|
+
# implemented this spec STILL passes, so doing the work never turns red.
|
|
3773
|
+
expect(ws_route.handler).to respond_to(:call)
|
|
3774
|
+
begin
|
|
3775
|
+
ws_route.handler.call(nil, :message, "hi")
|
|
3776
|
+
rescue NotImplementedError
|
|
3777
|
+
next # still the scaffold stub — expected until filled in
|
|
3778
|
+
end
|
|
3732
3779
|
end
|
|
3733
3780
|
end
|
|
3734
3781
|
RUBY
|
|
@@ -3759,8 +3806,16 @@ module Tina4
|
|
|
3759
3806
|
expect(Tina4::Events.listeners("#{event}").length).to be >= 1
|
|
3760
3807
|
end
|
|
3761
3808
|
|
|
3762
|
-
it "emitting reaches the listener (
|
|
3763
|
-
|
|
3809
|
+
it "emitting reaches the listener (green as a stub AND when filled)" do
|
|
3810
|
+
# Starts as a loud AI-FILL stub (strict emit re-raises
|
|
3811
|
+
# NotImplementedError); once implemented this spec STILL passes, so
|
|
3812
|
+
# doing the work never turns red.
|
|
3813
|
+
expect(Tina4::Events.listeners("#{event}").length).to be >= 1
|
|
3814
|
+
begin
|
|
3815
|
+
Tina4::Events.emit("#{event}", { "id" => 1 }, strict: true)
|
|
3816
|
+
rescue NotImplementedError
|
|
3817
|
+
next # still the scaffold stub — expected until filled in
|
|
3818
|
+
end
|
|
3764
3819
|
end
|
|
3765
3820
|
end
|
|
3766
3821
|
RUBY
|
data/lib/tina4/migration.rb
CHANGED
|
@@ -199,6 +199,23 @@ module Tina4
|
|
|
199
199
|
batch = next_batch_number
|
|
200
200
|
results = []
|
|
201
201
|
pending.each do |file|
|
|
202
|
+
# An empty, unedited scaffold migration — one that still bears a
|
|
203
|
+
# tina4:edit marker AND has no executable statement (the comment-only
|
|
204
|
+
# ALTER stub from a non-create migrate:create, or the view stub) — is
|
|
205
|
+
# unfinished. Applying it (and recording it passed) runs nothing and
|
|
206
|
+
# leaves the real SQL, written later under the same filename, never
|
|
207
|
+
# applied, with migrate reporting nothing pending (#288, #297). Keep it
|
|
208
|
+
# pending until written. A scaffolded CREATE TABLE carries the same hint
|
|
209
|
+
# but IS real DDL, so it applies — generate-model -> migrate is untouched.
|
|
210
|
+
if unapplied_scaffold?(file)
|
|
211
|
+
Tina4::Log.warning(
|
|
212
|
+
"Migration #{File.basename(file)} is still an unedited scaffold " \
|
|
213
|
+
"(a 'tina4:edit' placeholder with no SQL yet) — skipped and kept " \
|
|
214
|
+
"pending. Write the migration and remove the tina4:edit line(s), " \
|
|
215
|
+
"then run migrate again."
|
|
216
|
+
)
|
|
217
|
+
next
|
|
218
|
+
end
|
|
202
219
|
result = run_migration(file, batch)
|
|
203
220
|
results << result
|
|
204
221
|
# Stop on failure
|
|
@@ -207,6 +224,24 @@ module Tina4
|
|
|
207
224
|
results
|
|
208
225
|
end
|
|
209
226
|
|
|
227
|
+
# True if a migration file is still the unedited scaffold placeholder: it
|
|
228
|
+
# bears a tina4:edit marker AND (for a .sql file) has no executable
|
|
229
|
+
# statement. A .rb scaffold qualifies on the marker alone.
|
|
230
|
+
def unapplied_scaffold?(file)
|
|
231
|
+
raw = File.read(file)
|
|
232
|
+
return false unless raw =~ %r{(?:#|//|--|/\*|\{#)\s*tina4:edit\b}i
|
|
233
|
+
return true if file.end_with?(".rb")
|
|
234
|
+
!executable_sql?(raw)
|
|
235
|
+
rescue SystemCallError
|
|
236
|
+
false
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# True if any executable statement remains once SQL comments are stripped.
|
|
240
|
+
def executable_sql?(sql)
|
|
241
|
+
body = sql.gsub(%r{/\*.*?\*/}m, "").gsub(/--[^\n]*/, "")
|
|
242
|
+
!body.gsub(/[;\s]/, "").empty?
|
|
243
|
+
end
|
|
244
|
+
|
|
210
245
|
# A stable signed-64-bit key for PostgreSQL's pg_advisory_lock, derived from
|
|
211
246
|
# the lock name so it cannot collide with an application's own advisory-lock
|
|
212
247
|
# keys. It is a constant (not user input), so inlining it in the SQL is
|
data/lib/tina4/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.13.
|
|
4
|
+
version: 3.13.159
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-11-
|
|
11
|
+
date: 2026-11-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sqlite3
|