tina4ruby 3.13.127 → 3.13.128

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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/tina4/cli.rb +123 -1
  3. data/lib/tina4/version.rb +1 -1
  4. metadata +2 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0765c0228c99d3b8d5e26124c0afe5079ccc9ac675a3f067fc38e2e829bded7f
4
- data.tar.gz: 6134c34d7f1ea98ea558862e1f98dbb0532b2d6fb3156b8bad5b9e129e362475
3
+ metadata.gz: 63a254da7848fb136bf9c014d3ac291c31b9046ee2ad7bd40222189f5d8e031b
4
+ data.tar.gz: e9a23c45ec6b6c3ff46125454259447e743ffe6097a7866f486c6b5a8cc5aaad
5
5
  SHA512:
6
- metadata.gz: '0979843fa3654d0c48c62c19bbc8e5615a7b4b582286e14b3c517b76f3d9a50eb3246feb125b365b071c2c76adf7cdcbf93f802a576d75e5959d337cd19543c0'
7
- data.tar.gz: f5ba6720f96dee12b2b7d751282fba8835771a11b368afafd64bf311c9ca1d8bf8501fd3b28493dd42126dfb0b4159a4c50a407422d5b8f63b680b7b67dbe038
6
+ metadata.gz: dd3e42f53f1515ddfb137cd6713a11650309d4da359030cecde6be6368cad5309175b04943b25fa8bc5349e6b8505c2231a128b7de3c905c7a5605bf4d71c454
7
+ data.tar.gz: 81c929c2b3b06ce99682c440e1ce6385cb2bda7d836638e158fd0c0d2bcc41526eb7b7e055159c9aeab38468a16843d05b77b57f76beec525dab16ccc8cf9dec
data/lib/tina4/cli.rb CHANGED
@@ -6,6 +6,7 @@ require "set"
6
6
  require "json"
7
7
  require "tmpdir" # Dir.mktmpdir — used by the ADR-0063 sandbox edit-hint scan
8
8
  require "stringio" # StringIO — captures generator stdout during the sandbox run
9
+ require "open3" # Open3.capture2e — captures `ruby -c` output+status for `lint`
9
10
  require_relative "port_takeover"
10
11
 
11
12
  module Tina4
@@ -70,6 +71,7 @@ module Tina4
70
71
  "test" => { handler: :cmd_test, summary: "Run inline tests" },
71
72
  "queue" => { handler: :cmd_queue, usage: "<work|stats|retry|clear> [topic]", subcommands: QUEUE_SUBCOMMANDS.keys, summary: "Run queue workers and manage jobs" },
72
73
  "build" => { handler: :cmd_build, usage: "[--tag NAME] [--file PATH]", summary: "Build the deployable Docker image" },
74
+ "lint" => { handler: :cmd_lint, usage: "[--fix] [--no-install]", summary: "Lint src/ + app.rb (rubocop, installed dev-only on demand, else ruby -c)" },
73
75
  "version" => { handler: :cmd_version, summary: "Show Tina4 version" },
74
76
  "routes" => { handler: :cmd_routes, summary: "List all registered routes" },
75
77
  "console" => { handler: :cmd_console, summary: "Start an interactive console" },
@@ -516,7 +518,7 @@ module Tina4
516
518
  # Parse --key value and --flag from args. Returns [flags_hash, positional_array]
517
519
  def parse_flags(args)
518
520
  # Boolean-only flags that never take a value argument
519
- boolean_flags = %w[no-browser no-reload production managed all clear dev json public no-migration once dry-run]
521
+ boolean_flags = %w[no-browser no-reload production managed all clear dev json public no-migration once dry-run fix no-install]
520
522
 
521
523
  flags = {}
522
524
  positional = []
@@ -1206,6 +1208,126 @@ module Tina4
1206
1208
  puts " Run: docker run -p 7147:7147 #{tag}"
1207
1209
  end
1208
1210
 
1211
+ # ── lint ──────────────────────────────────────────────────────────────
1212
+ #
1213
+ # Lint the project's source. The framework ships NO linter (so a Tina4 app
1214
+ # stays zero-dependency); `tina4ruby lint` uses the project's own rubocop and
1215
+ # INSTALLS it as a DEV dependency on demand when it is absent. Layers:
1216
+ #
1217
+ # * rubocop present (bundled via the project Gemfile, or on PATH): run it.
1218
+ # `--fix` runs `rubocop -a` (safe autocorrect). rubocop reports syntax
1219
+ # too, so it is the whole pass when present.
1220
+ # * rubocop absent: silently `bundle add rubocop --group development` --
1221
+ # running `tina4ruby lint` is the consent to add it -- then run it. It
1222
+ # lands in the development group only, never the app's runtime
1223
+ # dependencies. `--no-install` skips this.
1224
+ # * Baseline (zero dependency): the built-in `ruby -c` syntax parse -- no
1225
+ # execution, nothing written, no dependency. Used with `--no-install` or
1226
+ # when the install cannot run (no bundler/Gemfile, offline).
1227
+ #
1228
+ # Contract (identical across all four frameworks): exit 0 = clean, non-zero =
1229
+ # findings; the summary names the tool that ran in [brackets]. Scope is the
1230
+ # user's app (`src/` recursively + `app.rb`), mirroring how `tina4ruby test`
1231
+ # runs the project's own specs -- not the framework's code. Ruby mirror of
1232
+ # the Python master's `_lint` (tina4-python/tina4_python/cli/__init__.py).
1233
+ #
1234
+ # tina4ruby lint # rubocop (installed dev-only on demand), else baseline
1235
+ # tina4ruby lint --fix # rubocop -a
1236
+ # tina4ruby lint --no-install # rubocop if already present, else the syntax baseline
1237
+ def cmd_lint(argv)
1238
+ flags, _positional = parse_flags(argv)
1239
+ fix = flags["fix"] == true
1240
+ no_install = flags["no-install"] == true
1241
+
1242
+ rb_files = []
1243
+ rb_files.concat(Dir.glob(File.join("src", "**", "*.rb")).sort) if File.directory?("src")
1244
+ rb_files << "app.rb" if File.file?("app.rb")
1245
+
1246
+ if rb_files.empty?
1247
+ puts " lint: nothing to lint (no src/ or app.rb)."
1248
+ exit 0
1249
+ end
1250
+
1251
+ rubocop = resolve_rubocop
1252
+
1253
+ # Silent on-demand install: running `tina4ruby lint` is the consent to add
1254
+ # rubocop as a DEV dependency of the project. --no-install opts out (CI /
1255
+ # offline) and falls through to the zero-dependency baseline. Needs bundler
1256
+ # AND a Gemfile to mutate.
1257
+ if rubocop.nil? && !no_install
1258
+ bundle = which_executable("bundle")
1259
+ if bundle && File.file?("Gemfile")
1260
+ puts " · rubocop not found -- adding it as a dev dependency (bundle add rubocop --group development)..."
1261
+ if system(bundle, "add", "rubocop", "--group", "development")
1262
+ rubocop = resolve_rubocop
1263
+ else
1264
+ puts " · could not install rubocop -- using the zero-dependency syntax baseline."
1265
+ end
1266
+ end
1267
+ end
1268
+
1269
+ if rubocop
1270
+ label = fix ? "rubocop -a" : "rubocop"
1271
+ command = rubocop + (fix ? ["-a"] : []) + rb_files
1272
+ ok = system(*command) # inherits stdio — rubocop prints its own report
1273
+ if ok
1274
+ puts " ✓ lint clean -- #{rb_files.length} file(s) [#{label}]"
1275
+ exit 0
1276
+ end
1277
+ puts " ✗ lint failed -- #{rb_files.length} file(s) [#{label}]"
1278
+ exit 1
1279
+ end
1280
+
1281
+ # Zero-dependency baseline: the built-in `ruby -c` syntax parse. No
1282
+ # execution, nothing written, no dependency added.
1283
+ puts " · syntax baseline has no autofix (--no-install, or rubocop unavailable)." if fix
1284
+
1285
+ syntax_errors = 0
1286
+ rb_files.each do |rel|
1287
+ output, status = Open3.capture2e("ruby", "-c", rel)
1288
+ next if status.success? # "Syntax OK"
1289
+
1290
+ syntax_errors += 1
1291
+ # `ruby -c` names the file+line in its first diagnostic line
1292
+ # (e.g. "ruby: src/broken.rb:2: syntax errors found (SyntaxError)"); drop
1293
+ # the redundant leading "ruby: " so the shape matches the Python master's
1294
+ # "file:lineno: msg".
1295
+ first_line = output.strip.sub(/\Aruby:\s*/, "").lines.first.to_s.rstrip
1296
+ first_line = "#{rel}: syntax error" if first_line.empty?
1297
+ puts " ✗ #{first_line}"
1298
+ end
1299
+
1300
+ if syntax_errors.positive?
1301
+ puts " ✗ lint failed -- #{syntax_errors} syntax error(s) in #{rb_files.length} file(s) [ruby -c]"
1302
+ exit 1
1303
+ end
1304
+ puts " ✓ lint clean -- #{rb_files.length} file(s) [ruby -c, syntax only]"
1305
+ exit 0
1306
+ end
1307
+
1308
+ # Resolve a runnable rubocop for the rich lint pass. Returns the argv PREFIX
1309
+ # to invoke it (`[<bundle>, "exec", "rubocop"]` when the project's Gemfile
1310
+ # bundles it — the version the project pins — or `[<rubocop-path>]` on PATH),
1311
+ # or nil to fall back to the `ruby -c` baseline. Mirrors the Python master's
1312
+ # `_resolve_ruff` (resolve the project's OWN linter). The bundler probe
1313
+ # (`bundle exec rubocop --version`) is a REAL resolve check: exit 0 only when
1314
+ # the gem is actually in the bundle. Bundler is preferred over a bare PATH
1315
+ # rubocop because inside a bundled project the pinned version is the correct
1316
+ # one (and a bare `rubocop` may trip Bundler's "not part of the bundle").
1317
+ def resolve_rubocop
1318
+ # Only the PROJECT's own bundled rubocop counts -- linting must be
1319
+ # reproducible from the project's pinned dev dependency, NOT a stray global
1320
+ # rubocop on PATH (which would also make the on-demand install
1321
+ # non-deterministic: a global tool would preempt it). When the project has
1322
+ # none, the caller installs it dev-only via `bundle add`.
1323
+ bundle = which_executable("bundle")
1324
+ if bundle && File.file?("Gemfile") &&
1325
+ system(bundle, "exec", "rubocop", "--version", out: File::NULL, err: File::NULL)
1326
+ return [bundle, "exec", "rubocop"]
1327
+ end
1328
+ nil
1329
+ end
1330
+
1209
1331
  # Locate an executable on PATH — the stdlib-only equivalent of Python's
1210
1332
  # shutil.which (no new gem). Honours PATHEXT on Windows.
1211
1333
  def which_executable(cmd)
data/lib/tina4/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.13.127"
4
+ VERSION = "3.13.128"
5
5
  end
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.127
4
+ version: 3.13.128
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-08-29 00:00:00.000000000 Z
11
+ date: 2026-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -276,20 +276,6 @@ dependencies:
276
276
  - - "~>"
277
277
  - !ruby/object:Gem::Version
278
278
  version: '3.12'
279
- - !ruby/object:Gem::Dependency
280
- name: rubocop
281
- requirement: !ruby/object:Gem::Requirement
282
- requirements:
283
- - - "~>"
284
- - !ruby/object:Gem::Version
285
- version: '1.50'
286
- type: :development
287
- prerelease: false
288
- version_requirements: !ruby/object:Gem::Requirement
289
- requirements:
290
- - - "~>"
291
- - !ruby/object:Gem::Version
292
- version: '1.50'
293
279
  - !ruby/object:Gem::Dependency
294
280
  name: openapi3_parser
295
281
  requirement: !ruby/object:Gem::Requirement