tng 0.4.7 → 0.4.9
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/README.md +291 -321
- data/Rakefile +8 -0
- data/bin/tng +292 -17
- data/binaries/go-ui-darwin-amd64 +0 -0
- data/binaries/go-ui-darwin-arm64 +0 -0
- data/binaries/go-ui-linux-amd64 +0 -0
- data/binaries/go-ui-linux-arm64 +0 -0
- data/binaries/tng-darwin-arm64.bundle +0 -0
- data/binaries/tng-darwin-x86_64.bundle +0 -0
- data/binaries/tng-linux-arm64.so +0 -0
- data/binaries/tng-linux-x86_64.so +0 -0
- data/binaries/tng.bundle +0 -0
- data/lib/generators/tng/install_generator.rb +1 -0
- data/lib/tng/services/direct_generation.rb +31 -2
- data/lib/tng/services/test_generator.rb +72 -8
- data/lib/tng/ui/go_ui_session.rb +78 -4
- data/lib/tng/ui/json_session.rb +18 -0
- data/lib/tng/version.rb +1 -1
- metadata +2 -2
data/Rakefile
CHANGED
|
@@ -75,9 +75,17 @@ task :redo do
|
|
|
75
75
|
# Code sign the bundles on macOS
|
|
76
76
|
if RUBY_PLATFORM.include?("darwin")
|
|
77
77
|
puts "🔐 Signing bundles..."
|
|
78
|
+
system("xattr -dr com.apple.quarantine binaries/ || true")
|
|
79
|
+
|
|
80
|
+
# Sign tng bundle
|
|
78
81
|
system("codesign --force --sign - #{source_file}")
|
|
79
82
|
system("codesign --force --sign - #{File.join(destination_dir, binary_name)}")
|
|
80
83
|
|
|
84
|
+
# Sign go-ui binaries if present
|
|
85
|
+
Dir.glob("binaries/go-ui-darwin-*").each do |binary|
|
|
86
|
+
system("codesign --force --sign - #{binary}")
|
|
87
|
+
end
|
|
88
|
+
|
|
81
89
|
# Also sign the gem installation if it exists
|
|
82
90
|
ruby_version = RUBY_VERSION.split(".")[0..1].join(".")
|
|
83
91
|
gem_bundle = File.expand_path("~/.local/share/mise/installs/ruby/#{RUBY_VERSION}/lib/ruby/gems/#{ruby_version}.0/gems/tng-*/binaries/tng.bundle")
|
data/bin/tng
CHANGED
|
@@ -48,6 +48,14 @@ class CLI
|
|
|
48
48
|
example "Duplicate Detection:", ""
|
|
49
49
|
example " bundle exec tng app/services/pricing_service.rb --clones", ""
|
|
50
50
|
example " bundle exec tng --file=users_controller.rb --clones --level=2", ""
|
|
51
|
+
example ""
|
|
52
|
+
example "Dead Code Detection:", ""
|
|
53
|
+
example " bundle exec tng app/models/user.rb --deadcode", ""
|
|
54
|
+
example " bundle exec tng --file=app/services/auth_service.rb -d", ""
|
|
55
|
+
example ""
|
|
56
|
+
example "X-Ray Visualization:", ""
|
|
57
|
+
example " bundle exec tng app/services/payment_processor.rb process_payment --xray", ""
|
|
58
|
+
example " bundle exec tng --file=users_controller.rb --method=create --xray --json", ""
|
|
51
59
|
end
|
|
52
60
|
|
|
53
61
|
option :file do
|
|
@@ -68,10 +76,16 @@ class CLI
|
|
|
68
76
|
desc "Run in clone detection mode (find code duplication)"
|
|
69
77
|
end
|
|
70
78
|
|
|
79
|
+
flag :deadcode do
|
|
80
|
+
short "-d"
|
|
81
|
+
long "--deadcode"
|
|
82
|
+
desc "Run in dead code detection mode (unreachable code, unused variables)"
|
|
83
|
+
end
|
|
84
|
+
|
|
71
85
|
option :level do
|
|
72
86
|
short "-l"
|
|
73
87
|
long "--level=LEVEL"
|
|
74
|
-
desc "Analysis level: 1 (Token), 2 (Structural), all (
|
|
88
|
+
desc "Analysis level: 1 (Token), 2 (Structural), 3 (Fuzzy), all (All levels)"
|
|
75
89
|
end
|
|
76
90
|
|
|
77
91
|
flag :audit do
|
|
@@ -85,6 +99,11 @@ class CLI
|
|
|
85
99
|
desc "Generate and visualize a symbolic trace"
|
|
86
100
|
end
|
|
87
101
|
|
|
102
|
+
flag :xray do
|
|
103
|
+
long "--xray"
|
|
104
|
+
desc "Generate X-Ray visualization (Mermaid flowchart)"
|
|
105
|
+
end
|
|
106
|
+
|
|
88
107
|
flag :json do
|
|
89
108
|
short "-j"
|
|
90
109
|
long "--json"
|
|
@@ -140,6 +159,10 @@ class CLI
|
|
|
140
159
|
handle_fix_command
|
|
141
160
|
elsif params[:trace] && params[:file]
|
|
142
161
|
run_direct_trace
|
|
162
|
+
elsif params[:deadcode] && params[:file]
|
|
163
|
+
run_dead_code_detection
|
|
164
|
+
elsif params[:xray] && params[:file]
|
|
165
|
+
run_direct_xray
|
|
143
166
|
elsif params[:file]
|
|
144
167
|
run_direct_generation
|
|
145
168
|
else
|
|
@@ -153,7 +176,10 @@ class CLI
|
|
|
153
176
|
return unless check_configuration
|
|
154
177
|
|
|
155
178
|
check_system_status
|
|
179
|
+
# puts "DEBUG: Verifying authentication..."
|
|
156
180
|
return unless check_authentication
|
|
181
|
+
# puts "DEBUG: Authentication verified."
|
|
182
|
+
|
|
157
183
|
|
|
158
184
|
clear_screen
|
|
159
185
|
@go_ui.show_banner(Tng::VERSION)
|
|
@@ -175,12 +201,16 @@ class CLI
|
|
|
175
201
|
normalized << "--audit"
|
|
176
202
|
when /^(?:--)?(trace)$/
|
|
177
203
|
normalized << "--trace"
|
|
204
|
+
when /^(?:--)?(xray)$/
|
|
205
|
+
normalized << "--xray"
|
|
178
206
|
when /^(?:--)?(json|j)$/
|
|
179
207
|
normalized << "--json"
|
|
180
208
|
when /^(?:--)?(fix|x)$/
|
|
181
209
|
normalized << "--fix"
|
|
182
210
|
when /^(?:--)?(clones|c)$/
|
|
183
211
|
normalized << "--clones"
|
|
212
|
+
when /^(?:--)?(deadcode|d)$/
|
|
213
|
+
normalized << "--deadcode"
|
|
184
214
|
when /^(?:--)?(level|l)=(.+)$/
|
|
185
215
|
normalized << "--level=#{::Regexp.last_match(2)}"
|
|
186
216
|
when /^(help|h)=(.+)$/
|
|
@@ -240,8 +270,12 @@ class CLI
|
|
|
240
270
|
handle_audit_method
|
|
241
271
|
when "trace"
|
|
242
272
|
handle_trace_method
|
|
273
|
+
when "xray"
|
|
274
|
+
handle_xray_method
|
|
243
275
|
when "clones"
|
|
244
276
|
handle_clone_detection
|
|
277
|
+
when "deadcode"
|
|
278
|
+
handle_dead_code_detection_menu
|
|
245
279
|
when "stats"
|
|
246
280
|
user_stats
|
|
247
281
|
when "about"
|
|
@@ -506,6 +540,30 @@ class CLI
|
|
|
506
540
|
@go_ui.show_audit_results(audit_data, "issues")
|
|
507
541
|
end
|
|
508
542
|
|
|
543
|
+
def run_dead_code_detection
|
|
544
|
+
file_path = params[:file]
|
|
545
|
+
unless File.exist?(file_path)
|
|
546
|
+
puts @pastel.red("Error: File '#{file_path}' not found.")
|
|
547
|
+
return
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
absolute_path = File.expand_path(file_path)
|
|
551
|
+
|
|
552
|
+
begin
|
|
553
|
+
# Always get JSON from Rust for flexibility
|
|
554
|
+
result = Tng.detect_dead_code(absolute_path, true)
|
|
555
|
+
|
|
556
|
+
if @go_ui
|
|
557
|
+
@go_ui.show_dead_code_results(absolute_path, result)
|
|
558
|
+
else
|
|
559
|
+
# Fallback to text output
|
|
560
|
+
puts Tng.detect_dead_code(absolute_path, false)
|
|
561
|
+
end
|
|
562
|
+
rescue => e
|
|
563
|
+
puts @pastel.red("Error detecting dead code: #{e.message}")
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
|
|
509
567
|
# Placeholder methods for other component types
|
|
510
568
|
def audit_model_method
|
|
511
569
|
models = nil
|
|
@@ -1169,13 +1227,18 @@ class CLI
|
|
|
1169
1227
|
end
|
|
1170
1228
|
|
|
1171
1229
|
def show_post_generation_menu(result)
|
|
1230
|
+
if @go_ui.is_a?(Tng::UI::JsonSession)
|
|
1231
|
+
@go_ui.show_generation_result(result)
|
|
1232
|
+
return
|
|
1233
|
+
end
|
|
1234
|
+
|
|
1172
1235
|
is_direct_mode = params[:file]
|
|
1173
1236
|
|
|
1174
1237
|
# Direct mode - just print info and exit
|
|
1175
1238
|
if is_direct_mode
|
|
1176
1239
|
puts "\n🚀 Test Generated Successfully!"
|
|
1177
1240
|
puts "📁 File: #{result[:file_path]}"
|
|
1178
|
-
puts "
|
|
1241
|
+
puts "▶\u{fe0f} Run: #{result[:run_command]}"
|
|
1179
1242
|
puts "✅ Test generation completed successfully!"
|
|
1180
1243
|
return
|
|
1181
1244
|
end
|
|
@@ -1196,19 +1259,19 @@ class CLI
|
|
|
1196
1259
|
end
|
|
1197
1260
|
end
|
|
1198
1261
|
|
|
1199
|
-
def copy_to_clipboard(command)
|
|
1262
|
+
def copy_to_clipboard(command, silent: false)
|
|
1200
1263
|
if RUBY_PLATFORM.include?("darwin")
|
|
1201
1264
|
IO.popen("pbcopy", "w") { |io| io.print command }
|
|
1202
|
-
@go_ui.show_clipboard_success(command)
|
|
1265
|
+
@go_ui.show_clipboard_success(command) unless silent
|
|
1203
1266
|
elsif RUBY_PLATFORM.include?("linux")
|
|
1204
1267
|
begin
|
|
1205
1268
|
IO.popen("xclip -selection clipboard", "w") { |io| io.print command }
|
|
1206
|
-
@go_ui.show_clipboard_success(command)
|
|
1269
|
+
@go_ui.show_clipboard_success(command) unless silent
|
|
1207
1270
|
rescue Errno::ENOENT
|
|
1208
|
-
puts "\n📋 Copy this command:\n#{command}\n"
|
|
1271
|
+
puts "\n📋 Copy this command:\n#{command}\n" unless silent
|
|
1209
1272
|
end
|
|
1210
1273
|
else
|
|
1211
|
-
puts "\n📋 Copy this command:\n#{command}\n"
|
|
1274
|
+
puts "\n📋 Copy this command:\n#{command}\n" unless silent
|
|
1212
1275
|
end
|
|
1213
1276
|
end
|
|
1214
1277
|
|
|
@@ -1255,6 +1318,21 @@ class CLI
|
|
|
1255
1318
|
end
|
|
1256
1319
|
end
|
|
1257
1320
|
|
|
1321
|
+
def handle_xray_method
|
|
1322
|
+
choice = @go_ui.show_test_type_menu("xray")
|
|
1323
|
+
|
|
1324
|
+
case choice
|
|
1325
|
+
when "controller"
|
|
1326
|
+
xray_controller_method
|
|
1327
|
+
when "model"
|
|
1328
|
+
xray_model_method
|
|
1329
|
+
when "service"
|
|
1330
|
+
xray_service_method
|
|
1331
|
+
when "other"
|
|
1332
|
+
xray_other_method
|
|
1333
|
+
end
|
|
1334
|
+
end
|
|
1335
|
+
|
|
1258
1336
|
def trace_controller_method
|
|
1259
1337
|
select_controller_and_method("Trace") do |controller, method_info|
|
|
1260
1338
|
run_trace_for_method(controller, method_info)
|
|
@@ -1279,6 +1357,30 @@ class CLI
|
|
|
1279
1357
|
end
|
|
1280
1358
|
end
|
|
1281
1359
|
|
|
1360
|
+
def xray_controller_method
|
|
1361
|
+
select_controller_and_method("X-Ray") do |controller, method_info|
|
|
1362
|
+
run_xray_for_method(controller, method_info)
|
|
1363
|
+
end
|
|
1364
|
+
end
|
|
1365
|
+
|
|
1366
|
+
def xray_model_method
|
|
1367
|
+
select_model_and_method("X-Ray") do |model, method_info|
|
|
1368
|
+
run_xray_for_method(model, method_info)
|
|
1369
|
+
end
|
|
1370
|
+
end
|
|
1371
|
+
|
|
1372
|
+
def xray_service_method
|
|
1373
|
+
select_service_and_method("X-Ray") do |service, method_info|
|
|
1374
|
+
run_xray_for_method(service, method_info)
|
|
1375
|
+
end
|
|
1376
|
+
end
|
|
1377
|
+
|
|
1378
|
+
def xray_other_method
|
|
1379
|
+
select_other_and_method("X-Ray") do |file, method_info|
|
|
1380
|
+
run_xray_for_method(file, method_info)
|
|
1381
|
+
end
|
|
1382
|
+
end
|
|
1383
|
+
|
|
1282
1384
|
def run_trace_for_method(file_info, method_info)
|
|
1283
1385
|
result_path = nil
|
|
1284
1386
|
|
|
@@ -1332,6 +1434,68 @@ class CLI
|
|
|
1332
1434
|
run_trace_for_method(file_info, method_info)
|
|
1333
1435
|
end
|
|
1334
1436
|
|
|
1437
|
+
def run_direct_xray
|
|
1438
|
+
return unless check_configuration
|
|
1439
|
+
|
|
1440
|
+
return unless check_authentication_configuration
|
|
1441
|
+
|
|
1442
|
+
file_path = params[:file]
|
|
1443
|
+
method_name = params[:method]
|
|
1444
|
+
|
|
1445
|
+
unless method_name
|
|
1446
|
+
puts @pastel.decorate("Please specify a method name with --method or -m", Tng::UI::Theme.color(:error))
|
|
1447
|
+
return
|
|
1448
|
+
end
|
|
1449
|
+
|
|
1450
|
+
full_path = File.expand_path(file_path)
|
|
1451
|
+
unless File.exist?(full_path)
|
|
1452
|
+
puts @pastel.decorate("File not found: #{full_path}", Tng::UI::Theme.color(:error))
|
|
1453
|
+
return
|
|
1454
|
+
end
|
|
1455
|
+
|
|
1456
|
+
file_info = { path: full_path, name: File.basename(full_path) }
|
|
1457
|
+
method_info = { name: method_name }
|
|
1458
|
+
|
|
1459
|
+
run_xray_for_method(file_info, method_info)
|
|
1460
|
+
end
|
|
1461
|
+
|
|
1462
|
+
def run_xray_for_method(file_info, method_info)
|
|
1463
|
+
xray_result = nil
|
|
1464
|
+
|
|
1465
|
+
@go_ui.show_spinner("Generating X-Ray for #{method_info[:name]}...") do
|
|
1466
|
+
begin
|
|
1467
|
+
type = Tng::Services::FileTypeDetector.detect_type(file_info[:path])
|
|
1468
|
+
file_object = Tng::Services::FileTypeDetector.build_file_object(file_info[:path], type)
|
|
1469
|
+
generator = Tng::Services::TestGenerator.new(@http_client)
|
|
1470
|
+
|
|
1471
|
+
xray_result = case type
|
|
1472
|
+
when "controller"
|
|
1473
|
+
generator.run_xray_for_controller_method(file_object, method_info)
|
|
1474
|
+
when "model"
|
|
1475
|
+
generator.run_xray_for_model_method(file_object, method_info)
|
|
1476
|
+
when "service"
|
|
1477
|
+
generator.run_xray_for_service_method(file_object, method_info)
|
|
1478
|
+
else
|
|
1479
|
+
generator.run_xray_for_other_method(file_object, method_info)
|
|
1480
|
+
end
|
|
1481
|
+
|
|
1482
|
+
if xray_result&.dig(:error)
|
|
1483
|
+
{ success: false, message: xray_result[:message] || "X-Ray failed" }
|
|
1484
|
+
else
|
|
1485
|
+
{ success: true, message: "X-Ray generated" }
|
|
1486
|
+
end
|
|
1487
|
+
rescue => e
|
|
1488
|
+
{ success: false, message: e.message }
|
|
1489
|
+
end
|
|
1490
|
+
end
|
|
1491
|
+
|
|
1492
|
+
return @go_ui.show_auth_error(xray_result[:message] || "X-Ray failed") if xray_result&.dig(:error)
|
|
1493
|
+
|
|
1494
|
+
if xray_result && xray_result[:mermaid_code]
|
|
1495
|
+
@go_ui.show_xray_results(xray_result, method_info)
|
|
1496
|
+
end
|
|
1497
|
+
end
|
|
1498
|
+
|
|
1335
1499
|
def select_controller_and_method(action_name)
|
|
1336
1500
|
controllers = nil
|
|
1337
1501
|
@go_ui.show_spinner("Analyzing controllers...") do
|
|
@@ -1470,18 +1634,129 @@ class CLI
|
|
|
1470
1634
|
choice = files.find { |f| f[:name] == selected }
|
|
1471
1635
|
next unless choice
|
|
1472
1636
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
end
|
|
1637
|
+
# For dead code, we don't need to select a method, just the file.
|
|
1638
|
+
# But this helper yields (file_choice, method_choice).
|
|
1639
|
+
# We might need a simpler selector for dead code if it's file-based only.
|
|
1640
|
+
# Re-using method selector for now if we want per-method, but dead code is usually per-file.
|
|
1478
1641
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
next if m_selected == "back"
|
|
1642
|
+
# Actually, dead code analysis is whole-file.
|
|
1643
|
+
# So we should probably just return the file choice.
|
|
1482
1644
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1645
|
+
# Let's implement specific helpers for dead code that just pick files.
|
|
1646
|
+
end
|
|
1647
|
+
end
|
|
1648
|
+
|
|
1649
|
+
def handle_dead_code_detection_menu
|
|
1650
|
+
# Pick a file type first to narrow down
|
|
1651
|
+
choice = @go_ui.show_test_type_menu("deadcode")
|
|
1652
|
+
return if choice == "back"
|
|
1653
|
+
|
|
1654
|
+
case choice
|
|
1655
|
+
when "controller"
|
|
1656
|
+
detect_dead_code_for_type("app/controllers", Tng::Analyzers::Controller)
|
|
1657
|
+
when "model"
|
|
1658
|
+
detect_dead_code_for_type("app/models", Tng::Analyzers::Model)
|
|
1659
|
+
when "service"
|
|
1660
|
+
# Service analyzer is a bit different, it scans app/services usually
|
|
1661
|
+
detect_dead_code_for_service
|
|
1662
|
+
when "other"
|
|
1663
|
+
detect_dead_code_for_other
|
|
1664
|
+
end
|
|
1665
|
+
end
|
|
1666
|
+
|
|
1667
|
+
def detect_dead_code_for_type(dir, analyzer_class)
|
|
1668
|
+
files = nil
|
|
1669
|
+
@go_ui.show_spinner("Analyzing files in #{dir}...") do
|
|
1670
|
+
files = analyzer_class.files_in_dir(dir).map do |file|
|
|
1671
|
+
relative_path = file[:path].gsub(%r{^.*#{dir}/}, "").gsub(".rb", "")
|
|
1672
|
+
namespaced_name = relative_path.split("/").map(&:camelize).join("::")
|
|
1673
|
+
{ name: namespaced_name, path: file[:path] }
|
|
1674
|
+
end
|
|
1675
|
+
{ success: true, message: "Found #{files.length} files" }
|
|
1676
|
+
end
|
|
1677
|
+
|
|
1678
|
+
return @go_ui.show_no_items(dir) if files.empty?
|
|
1679
|
+
|
|
1680
|
+
items = files.map { |f| { name: f[:name], path: f[:path] } }
|
|
1681
|
+
|
|
1682
|
+
loop do
|
|
1683
|
+
selected = @go_ui.show_list_view("Select File to Analyze", items)
|
|
1684
|
+
return if selected == "back"
|
|
1685
|
+
|
|
1686
|
+
choice = files.find { |f| f[:name] == selected }
|
|
1687
|
+
next unless choice
|
|
1688
|
+
|
|
1689
|
+
# Run detection on the file
|
|
1690
|
+
run_dead_code_detection_interactive(choice[:path])
|
|
1691
|
+
end
|
|
1692
|
+
end
|
|
1693
|
+
|
|
1694
|
+
def detect_dead_code_for_service
|
|
1695
|
+
services = nil
|
|
1696
|
+
@go_ui.show_spinner("Analyzing services...") do
|
|
1697
|
+
services = Tng::Analyzers::Service.files_in_dir.map do |file|
|
|
1698
|
+
relative_path = file[:path].gsub(%r{^.*app/services?/}, "").gsub(".rb", "")
|
|
1699
|
+
namespaced_name = relative_path.split("/").map(&:camelize).join("::")
|
|
1700
|
+
{ name: namespaced_name, path: file[:path] }
|
|
1701
|
+
end
|
|
1702
|
+
{ success: true, message: "Found #{services.length} services" }
|
|
1703
|
+
end
|
|
1704
|
+
|
|
1705
|
+
return @go_ui.show_no_items("services") if services.empty?
|
|
1706
|
+
|
|
1707
|
+
items = services.map { |s| { name: s[:name], path: s[:path] } }
|
|
1708
|
+
|
|
1709
|
+
loop do
|
|
1710
|
+
selected = @go_ui.show_list_view("Select Service to Analyze", items)
|
|
1711
|
+
return if selected == "back"
|
|
1712
|
+
|
|
1713
|
+
choice = services.find { |s| s[:name] == selected }
|
|
1714
|
+
next unless choice
|
|
1715
|
+
|
|
1716
|
+
run_dead_code_detection_interactive(choice[:path])
|
|
1717
|
+
end
|
|
1718
|
+
end
|
|
1719
|
+
|
|
1720
|
+
def detect_dead_code_for_other
|
|
1721
|
+
files = nil
|
|
1722
|
+
@go_ui.show_spinner("Analyzing other files...") do
|
|
1723
|
+
files = Tng::Analyzers::Other.files_in_dir.map do |file|
|
|
1724
|
+
relative_path = file[:relative_path].gsub(".rb", "")
|
|
1725
|
+
namespaced_name = relative_path.split("/").map(&:camelize).join("::")
|
|
1726
|
+
{ name: namespaced_name, path: file[:path], type: file[:type] }
|
|
1727
|
+
end
|
|
1728
|
+
{ success: true, message: "Found #{files.length} other files" }
|
|
1729
|
+
end
|
|
1730
|
+
|
|
1731
|
+
return @go_ui.show_no_items("other files") if files.empty?
|
|
1732
|
+
|
|
1733
|
+
items = files.map { |f| { name: f[:name], path: f[:path] } }
|
|
1734
|
+
|
|
1735
|
+
loop do
|
|
1736
|
+
selected = @go_ui.show_list_view("Select File to Analyze", items)
|
|
1737
|
+
return if selected == "back"
|
|
1738
|
+
|
|
1739
|
+
choice = files.find { |f| f[:name] == selected }
|
|
1740
|
+
next unless choice
|
|
1741
|
+
|
|
1742
|
+
run_dead_code_detection_interactive(choice[:path])
|
|
1743
|
+
end
|
|
1744
|
+
end
|
|
1745
|
+
|
|
1746
|
+
def run_dead_code_detection_interactive(path)
|
|
1747
|
+
@go_ui.show_spinner("Detecting dead code...") do
|
|
1748
|
+
# Just a visual delay or setup if needed
|
|
1749
|
+
sleep(0.5)
|
|
1750
|
+
{ success: true, message: "Analysis complete" }
|
|
1751
|
+
end
|
|
1752
|
+
|
|
1753
|
+
# Reuse the CLI logic but bypass params
|
|
1754
|
+
absolute_path = File.expand_path(path)
|
|
1755
|
+
begin
|
|
1756
|
+
result = Tng.detect_dead_code(absolute_path, true)
|
|
1757
|
+
@go_ui.show_dead_code_results(absolute_path, result)
|
|
1758
|
+
rescue => e
|
|
1759
|
+
@go_ui.display_error("Error detecting dead code: #{e.message}")
|
|
1485
1760
|
end
|
|
1486
1761
|
end
|
|
1487
1762
|
end
|
data/binaries/go-ui-darwin-amd64
CHANGED
|
Binary file
|
data/binaries/go-ui-darwin-arm64
CHANGED
|
Binary file
|
data/binaries/go-ui-linux-amd64
CHANGED
|
Binary file
|
data/binaries/go-ui-linux-arm64
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/binaries/tng-linux-arm64.so
CHANGED
|
Binary file
|
|
Binary file
|
data/binaries/tng.bundle
CHANGED
|
Binary file
|
|
@@ -96,19 +96,41 @@ module Tng
|
|
|
96
96
|
return
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
action =
|
|
100
|
+
if @params[:trace]
|
|
101
|
+
"Tracing"
|
|
102
|
+
elsif @params[:xray]
|
|
103
|
+
"Generating X-Ray for"
|
|
104
|
+
elsif @params[:audit]
|
|
105
|
+
"Auditing"
|
|
106
|
+
else
|
|
107
|
+
"Generating test for"
|
|
108
|
+
end
|
|
109
|
+
@go_ui.display_info(@pastel.bright_white("🎯 #{action} #{file_object[:name]}##{method_info[:name]}..."))
|
|
100
110
|
|
|
101
111
|
result = @go_ui.show_progress("Processing...") do |progress|
|
|
102
112
|
generate_test_result(file_object, method_info, type, progress)
|
|
103
113
|
end
|
|
104
114
|
|
|
105
115
|
if result&.dig(:error)
|
|
106
|
-
|
|
116
|
+
label =
|
|
117
|
+
if @params[:trace]
|
|
118
|
+
"Trace"
|
|
119
|
+
elsif @params[:xray]
|
|
120
|
+
"X-Ray"
|
|
121
|
+
elsif @params[:audit]
|
|
122
|
+
"Audit"
|
|
123
|
+
else
|
|
124
|
+
"Test generation"
|
|
125
|
+
end
|
|
126
|
+
@go_ui.display_error(@pastel.red("❌ #{label} failed: #{result[:message]}"))
|
|
107
127
|
elsif result&.dig(:trace_generated)
|
|
108
128
|
if result[:file_path]
|
|
109
129
|
@go_ui.show_trace_results(result[:file_path])
|
|
110
130
|
File.unlink(result[:file_path]) if File.exist?(result[:file_path])
|
|
111
131
|
end
|
|
132
|
+
elsif @params[:xray]
|
|
133
|
+
@go_ui.show_xray_results(result, method_info) if result
|
|
112
134
|
elsif @params[:audit]
|
|
113
135
|
display_audit_results(result)
|
|
114
136
|
elsif result && result[:file_path]
|
|
@@ -147,6 +169,13 @@ module Tng
|
|
|
147
169
|
|
|
148
170
|
if @params[:trace]
|
|
149
171
|
perform_trace_analysis(file_object, method_info, progress)
|
|
172
|
+
elsif @params[:xray]
|
|
173
|
+
case type
|
|
174
|
+
when "controller" then generator.run_xray_for_controller_method(file_object, method_info, progress: progress)
|
|
175
|
+
when "model" then generator.run_xray_for_model_method(file_object, method_info, progress: progress)
|
|
176
|
+
when "service" then generator.run_xray_for_service_method(file_object, method_info, progress: progress)
|
|
177
|
+
else generator.run_xray_for_other_method(file_object, method_info, progress: progress)
|
|
178
|
+
end
|
|
150
179
|
elsif @params[:audit]
|
|
151
180
|
case type
|
|
152
181
|
when "controller" then generator.run_audit_for_controller_method(file_object, method_info, progress: progress)
|
|
@@ -36,6 +36,7 @@ module Tng
|
|
|
36
36
|
generate_test_for_type(other_file, method_info, :other, progress: progress)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
|
|
39
40
|
# Audit mode - async, polls for completion
|
|
40
41
|
def run_audit_for_controller_method(controller, method_info, progress: nil)
|
|
41
42
|
run_audit_for_type(controller, method_info, :controller, progress: progress)
|
|
@@ -53,6 +54,23 @@ module Tng
|
|
|
53
54
|
run_audit_for_type(other_file, method_info, :other, progress: progress)
|
|
54
55
|
end
|
|
55
56
|
|
|
57
|
+
# X-Ray mode - async, polls for completion
|
|
58
|
+
def run_xray_for_controller_method(controller, method_info, progress: nil)
|
|
59
|
+
run_xray_for_type(controller, method_info, :controller, progress: progress)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def run_xray_for_model_method(model, method_info, progress: nil)
|
|
63
|
+
run_xray_for_type(model, method_info, :model, progress: progress)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def run_xray_for_service_method(service, method_info, progress: nil)
|
|
67
|
+
run_xray_for_type(service, method_info, :service, progress: progress)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def run_xray_for_other_method(other_file, method_info, progress: nil)
|
|
71
|
+
run_xray_for_type(other_file, method_info, :other, progress: progress)
|
|
72
|
+
end
|
|
73
|
+
|
|
56
74
|
def run_audit_for_type(file_object, method_info, type, progress: nil)
|
|
57
75
|
response = send_audit_request_for_type(file_object, method_info, type)
|
|
58
76
|
return { error: :network_error, message: "Network error" } unless response
|
|
@@ -90,13 +108,59 @@ module Tng
|
|
|
90
108
|
# Pass audit_mode: true as 8th parameter
|
|
91
109
|
case type
|
|
92
110
|
when :controller
|
|
93
|
-
Tng.send_request_for_controller(name, file_object[:path], method_info, *config, true)
|
|
111
|
+
Tng.send_request_for_controller(name, file_object[:path], method_info, *config, true, false)
|
|
112
|
+
when :model
|
|
113
|
+
Tng.send_request_for_model(name, file_object[:path], method_info, *config, true, false)
|
|
114
|
+
when :service
|
|
115
|
+
Tng.send_request_for_service(name, file_object[:path], method_info, *config, true, false)
|
|
116
|
+
when :other
|
|
117
|
+
Tng.send_request_for_other(name, file_object[:path], method_info, *config, true, false)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def run_xray_for_type(file_object, method_info, type, progress: nil)
|
|
122
|
+
response = send_xray_request_for_type(file_object, method_info, type)
|
|
123
|
+
return { error: :network_error, message: "Network error" } unless response
|
|
124
|
+
|
|
125
|
+
if response.is_a?(HTTPX::ErrorResponse)
|
|
126
|
+
return { error: :network_error, message: response.error&.message || "Network error" }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
return { error: :auth_failed, message: "Invalid or missing API key" } if response.status == 401
|
|
130
|
+
return { error: :auth_failed, message: "API key expired" } if response.status == 403
|
|
131
|
+
|
|
132
|
+
begin
|
|
133
|
+
job_data = JSON.parse(response.body)
|
|
134
|
+
return { error: :server_error, message: job_data["error"] } if job_data["error"]
|
|
135
|
+
|
|
136
|
+
job_id = job_data["job_id"]
|
|
137
|
+
return { error: :server_error, message: "No job_id returned" } unless job_id
|
|
138
|
+
|
|
139
|
+
# Poll for completion (similar to audit)
|
|
140
|
+
result = poll_for_completion(job_id, progress: progress)
|
|
141
|
+
return { error: :timeout, message: "X-Ray generation timed out" } unless result
|
|
142
|
+
|
|
143
|
+
# X-Ray results should contain mermaid_code and explanation
|
|
144
|
+
result
|
|
145
|
+
rescue JSON::ParserError => e
|
|
146
|
+
{ error: :parse_error, message: "Failed to parse X-Ray response: #{e.message}" }
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def send_xray_request_for_type(file_object, method_info, type)
|
|
151
|
+
config = request_config
|
|
152
|
+
name = file_object[:name] || File.basename(file_object[:path], ".rb")
|
|
153
|
+
|
|
154
|
+
# Pass xray_mode: true as 9th parameter (audit_mode: false, xray_mode: true)
|
|
155
|
+
case type
|
|
156
|
+
when :controller
|
|
157
|
+
Tng.send_request_for_controller(name, file_object[:path], method_info, *config, false, true)
|
|
94
158
|
when :model
|
|
95
|
-
Tng.send_request_for_model(name, file_object[:path], method_info, *config, true)
|
|
159
|
+
Tng.send_request_for_model(name, file_object[:path], method_info, *config, false, true)
|
|
96
160
|
when :service
|
|
97
|
-
Tng.send_request_for_service(name, file_object[:path], method_info, *config, true)
|
|
161
|
+
Tng.send_request_for_service(name, file_object[:path], method_info, *config, false, true)
|
|
98
162
|
when :other
|
|
99
|
-
Tng.send_request_for_other(name, file_object[:path], method_info, *config, true)
|
|
163
|
+
Tng.send_request_for_other(name, file_object[:path], method_info, *config, false, true)
|
|
100
164
|
end
|
|
101
165
|
end
|
|
102
166
|
|
|
@@ -144,13 +208,13 @@ module Tng
|
|
|
144
208
|
# Pass audit_mode: false as 8th parameter for normal test generation
|
|
145
209
|
case type
|
|
146
210
|
when :controller
|
|
147
|
-
Tng.send_request_for_controller(name, file_object[:path], method_info, *config, false)
|
|
211
|
+
Tng.send_request_for_controller(name, file_object[:path], method_info, *config, false, false)
|
|
148
212
|
when :model
|
|
149
|
-
Tng.send_request_for_model(name, file_object[:path], method_info, *config, false)
|
|
213
|
+
Tng.send_request_for_model(name, file_object[:path], method_info, *config, false, false)
|
|
150
214
|
when :service
|
|
151
|
-
Tng.send_request_for_service(name, file_object[:path], method_info, *config, false)
|
|
215
|
+
Tng.send_request_for_service(name, file_object[:path], method_info, *config, false, false)
|
|
152
216
|
when :other
|
|
153
|
-
Tng.send_request_for_other(name, file_object[:path], method_info, *config, false)
|
|
217
|
+
Tng.send_request_for_other(name, file_object[:path], method_info, *config, false, false)
|
|
154
218
|
end
|
|
155
219
|
end
|
|
156
220
|
|