diff --git a/.claude/hooks/rtk-suggest.sh b/.claude/hooks/rtk-suggest.sh index 34fb50f3b..80c356582 100755 --- a/.claude/hooks/rtk-suggest.sh +++ b/.claude/hooks/rtk-suggest.sh @@ -97,10 +97,8 @@ elif echo "$FIRST_CMD" | grep -qE '^head\s+'; then fi # --- JS/TS tooling --- -elif echo "$FIRST_CMD" | grep -qE '^(pnpm\s+)?vitest(\s|$)'; then - SUGGESTION="rtk vitest run" -elif echo "$FIRST_CMD" | grep -qE '^pnpm\s+test(\s|$)'; then - SUGGESTION="rtk vitest run" +elif echo "$FIRST_CMD" | grep -qE '^(pnpm\s+)?vitest(\s+run)?(\s|$)'; then + SUGGESTION="rtk vitest" elif echo "$FIRST_CMD" | grep -qE '^pnpm\s+tsc(\s|$)'; then SUGGESTION="rtk tsc" elif echo "$FIRST_CMD" | grep -qE '^(npx\s+)?tsc(\s|$)'; then diff --git a/INSTALL.md b/INSTALL.md index 98457d09a..f439d4e74 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -231,11 +231,11 @@ rtk ls . # Test with git rtk git status -# Test with pnpm (fork only) +# Test with pnpm rtk pnpm list -# Test with Vitest (feat/vitest-support branch only) -rtk vitest run +# Test with Vitest +rtk vitest ``` ## Uninstalling @@ -303,8 +303,15 @@ rtk pnpm install pkg # Silent installation ### Tests ```bash -rtk test cargo test # Failures only (-90%) -rtk vitest run # Filtered Vitest output (-99.6%) +rtk cargo test # Filtered Cargo test output (-90%) +rtk go test # Filtered Go tests (NDJSON, -90%) +rtk jest # Filtered Jest output (-99.6%) +rtk vitest # Filtered Vitest output (-99.6%) +rtk playwright test # Filtered Playwright output (-94%) +rtk pytest # Filtered Python tests (-90%) +rtk rake test # Filtered Ruby tests (-90%) +rtk rspec # Filtered RSpec tests (-60%) +rtk test # Generic test wrapper - failures only (-90%) ``` ### Statistics @@ -319,7 +326,7 @@ rtk gain --history # With command history ### Production T3 Stack Project | Operation | Standard | RTK | Reduction | |-----------|----------|-----|-----------| -| `vitest run` | 102,199 chars | 377 chars | **-99.6%** | +| `vitest` | 102,199 chars | 377 chars | **-99.6%** | | `git status` | 529 chars | 217 chars | **-59%** | | `pnpm list` | ~8,000 tokens | ~2,400 | **-70%** | | `pnpm outdated` | ~12,000 tokens | ~1,200-2,400 | **-80-90%** | diff --git a/README.md b/README.md index 9772104c3..2c07c33d0 100644 --- a/README.md +++ b/README.md @@ -169,15 +169,16 @@ rtk gh run list # Workflow run status ### Test Runners ```bash -rtk test cargo test # Show failures only (-90%) -rtk err npm run build # Errors/warnings only -rtk vitest run # Vitest compact (failures only) +rtk jest # Jest compact (failures only) +rtk vitest # Vitest compact (failures only) rtk playwright test # E2E results (failures only) rtk pytest # Python tests (-90%) rtk go test # Go tests (NDJSON, -90%) rtk cargo test # Cargo tests (-90%) rtk rake test # Ruby minitest (-90%) rtk rspec # RSpec tests (JSON, -60%+) +rtk err # Filter errors only from any command +rtk test # Generic test wrapper - failures only (-90%) ``` ### Build & Lint diff --git a/README_es.md b/README_es.md index 27237ace1..751bd1ba8 100644 --- a/README_es.md +++ b/README_es.md @@ -121,10 +121,12 @@ rtk git push # -> "ok main" ### Tests ```bash -rtk test cargo test # Solo fallos (-90%) -rtk vitest run # Vitest compacto +rtk jest # Jest compacto +rtk vitest # Vitest compacto rtk pytest # Tests Python (-90%) rtk go test # Tests Go (-90%) +rtk cargo test # Tests Rust (-90%) +rtk test # Solo fallos (-90%) ``` ### Build & Lint diff --git a/README_fr.md b/README_fr.md index a29f52e55..d305feaaf 100644 --- a/README_fr.md +++ b/README_fr.md @@ -135,11 +135,12 @@ rtk git push # -> "ok main" ### Tests ```bash -rtk test cargo test # Echecs uniquement (-90%) -rtk vitest run # Vitest compact +rtk jest # Jest compact +rtk vitest # Vitest compact rtk pytest # Tests Python (-90%) rtk go test # Tests Go (-90%) rtk cargo test # Tests Cargo (-90%) +rtk test # Echecs uniquement (-90%) ``` ### Build & Lint diff --git a/README_ja.md b/README_ja.md index e0db09809..23bf4412f 100644 --- a/README_ja.md +++ b/README_ja.md @@ -121,10 +121,11 @@ rtk git push # -> "ok main" ### テスト ```bash -rtk test cargo test # 失敗のみ表示(-90%) -rtk vitest run # Vitest コンパクト +rtk jest # Jest コンパクト +rtk vitest # Vitest コンパクト rtk pytest # Python テスト(-90%) rtk go test # Go テスト(-90%) +rtk test # 失敗のみ表示(-90%) ``` ### ビルド & リント diff --git a/README_ko.md b/README_ko.md index abc35a14a..a07a4590a 100644 --- a/README_ko.md +++ b/README_ko.md @@ -121,10 +121,11 @@ rtk git push # -> "ok main" ### 테스트 ```bash -rtk test cargo test # 실패만 표시 (-90%) -rtk vitest run # Vitest 컴팩트 +rtk jest # Jest 컴팩트 +rtk vitest # Vitest 컴팩트 rtk pytest # Python 테스트 (-90%) rtk go test # Go 테스트 (-90%) +rtk test # 실패만 표시 (-90%) ``` ### 빌드 & 린트 diff --git a/README_zh.md b/README_zh.md index c4a358d77..854ca2314 100644 --- a/README_zh.md +++ b/README_zh.md @@ -122,10 +122,11 @@ rtk git push # -> "ok main" ### 测试 ```bash -rtk test cargo test # 仅显示失败(-90%) -rtk vitest run # Vitest 紧凑输出 +rtk jest # Jest 紧凑输出 +rtk vitest # Vitest 紧凑输出 rtk pytest # Python 测试(-90%) rtk go test # Go 测试(-90%) +rtk test # 仅显示失败(-90%) ``` ### 构建 & 检查 diff --git a/docs/guide/analytics/gain.md b/docs/guide/analytics/gain.md index db2249d4b..9b257e3e5 100644 --- a/docs/guide/analytics/gain.md +++ b/docs/guide/analytics/gain.md @@ -94,7 +94,8 @@ Same columns as daily, aggregated by Sunday-Saturday week or calendar month. |---------|----------------|-----------| | `git status` | 77-93% | Compact stat format | | `eslint` | 84% | Group by rule | -| `vitest run` | 94-99% | Show failures only | +| `jest` | 94-99% | Show failures only | +| `vitest` | 94-99% | Show failures only | | `find` | 75% | Tree format | | `pnpm list` | 70-90% | Compact dependencies | | `grep` | 70% | Truncate + group | diff --git a/docs/guide/what-rtk-covers.md b/docs/guide/what-rtk-covers.md index 426e91a97..de20182f2 100644 --- a/docs/guide/what-rtk-covers.md +++ b/docs/guide/what-rtk-covers.md @@ -51,7 +51,8 @@ Once RTK is installed with a hook, these commands are automatically intercepted | Command | Savings | What changes | |---------|---------|--------------| -| `vitest run` | 94-99% | Failures only | +| `jest` | 94-99% | Failures only | +| `vitest` | 94-99% | Failures only | | `tsc` | 75% | Type errors grouped by file | | `eslint` | 84% | Violations grouped by rule | | `pnpm list` | 70-90% | Compact dependency tree | diff --git a/docs/usage/AUDIT_GUIDE.md b/docs/usage/AUDIT_GUIDE.md index 4ce6fecec..c653fc08a 100644 --- a/docs/usage/AUDIT_GUIDE.md +++ b/docs/usage/AUDIT_GUIDE.md @@ -267,7 +267,8 @@ Savings % = (Saved / Input) × 100 |---------|----------------|-----------| | `rtk git status` | 77-93% | Compact stat format | | `rtk eslint` | 84% | Group by rule | -| `rtk vitest run` | 94-99% | Show failures only | +| `rtk jest` | 94-99% | Show failures only | +| `rtk vitest` | 94-99% | Show failures only | | `rtk find` | 75% | Tree format | | `rtk pnpm list` | 70-90% | Compact dependencies | | `rtk grep` | 70% | Truncate + group | diff --git a/docs/usage/FEATURES.md b/docs/usage/FEATURES.md index 061a604a9..3c285cb40 100644 --- a/docs/usage/FEATURES.md +++ b/docs/usage/FEATURES.md @@ -576,12 +576,13 @@ Filtre la sortie de `cargo nextest` pour n'afficher que les echecs. --- -### `rtk vitest run` -- Tests Vitest +### `rtk jest` / `rtk vitest` -- Tests Jest/Vitest **Economies :** ~99.5% ```bash -rtk vitest run [args...] +rtk jest [args...] +rtk vitest [args...] ``` --- @@ -1258,7 +1259,8 @@ rtk verify | `ls` | `rtk ls` | | `tree` | `rtk tree` | | `wc` | `rtk wc` | -| `vitest/jest` | `rtk vitest run` | +| `jest` | `rtk jest` | +| `vitest` | `rtk vitest` | | `tsc` | `rtk tsc` | | `eslint/biome` | `rtk lint` | | `prettier` | `rtk prettier` | diff --git a/hooks/claude/test-rtk-rewrite.sh b/hooks/claude/test-rtk-rewrite.sh index 85103163b..702fe9299 100644 --- a/hooks/claude/test-rtk-rewrite.sh +++ b/hooks/claude/test-rtk-rewrite.sh @@ -117,6 +117,10 @@ test_rewrite "npx prisma migrate" \ "npx prisma migrate" \ "rtk prisma migrate" +test_rewrite "rtk git status" \ + "rtk git status" \ + "rtk git status" + echo "" # ---- SECTION 2: Env var prefix handling (THE BIG FIX) ---- @@ -134,8 +138,8 @@ test_rewrite "env + git log" \ "GIT_PAGER=cat rtk git log --oneline -10" test_rewrite "multi env + vitest" \ - "NODE_ENV=test CI=1 npx vitest run" \ - "NODE_ENV=test CI=1 rtk vitest run" + "NODE_ENV=test CI=1 npx vitest" \ + "NODE_ENV=test CI=1 rtk vitest" test_rewrite "env + ls" \ "LANG=C ls -la" \ @@ -143,7 +147,7 @@ test_rewrite "env + ls" \ test_rewrite "env + npm run" \ "NODE_ENV=test npm run test:e2e" \ - "NODE_ENV=test rtk npm test:e2e" + "NODE_ENV=test rtk npm run test:e2e" test_rewrite "env + docker compose (unsupported subcommand, NOT rewritten)" \ "COMPOSE_PROJECT_NAME=test docker compose up -d" \ @@ -159,23 +163,15 @@ echo "" echo "--- New patterns ---" test_rewrite "npm run test:e2e" \ "npm run test:e2e" \ - "rtk npm test:e2e" + "rtk npm run test:e2e" test_rewrite "npm run build" \ "npm run build" \ - "rtk npm build" + "rtk npm run build" -test_rewrite "npm test" \ - "npm test" \ - "rtk npm test" - -test_rewrite "vue-tsc -b" \ - "vue-tsc -b" \ - "rtk tsc -b" - -test_rewrite "npx vue-tsc --noEmit" \ - "npx vue-tsc --noEmit" \ - "rtk tsc --noEmit" +test_rewrite "npm jest run" \ + "npm jest run" \ + "rtk jest" test_rewrite "docker compose up -d (NOT rewritten — unsupported by rtk)" \ "docker compose up -d" \ @@ -209,17 +205,17 @@ test_rewrite "docker exec -it db psql" \ "docker exec -it db psql" \ "rtk docker exec -it db psql" -test_rewrite "find (NOT rewritten — different arg format)" \ +test_rewrite "find . -name '*.ts'" \ "find . -name '*.ts'" \ - "" + "rtk find . -name '*.ts'" -test_rewrite "tree (NOT rewritten — different arg format)" \ +test_rewrite "tree src/" \ "tree src/" \ - "" + "rtk tree src/" -test_rewrite "wget (NOT rewritten — different arg format)" \ +test_rewrite "wget https://example.com/file" \ "wget https://example.com/file" \ - "" + "rtk wget https://example.com/file" test_rewrite "gh api repos/owner/repo" \ "gh api repos/owner/repo" \ @@ -281,32 +277,28 @@ echo "" echo "--- Vitest run dedup ---" test_rewrite "vitest (no args)" \ "vitest" \ - "rtk vitest run" + "rtk vitest" -test_rewrite "vitest run (no double run)" \ +test_rewrite "vitest run (no run)" \ "vitest run" \ - "rtk vitest run" + "rtk vitest" -test_rewrite "vitest run --reporter" \ - "vitest run --reporter=verbose" \ - "rtk vitest run --reporter=verbose" +test_rewrite "vitest --reporter" \ + "vitest --reporter=verbose" \ + "rtk vitest --reporter=verbose" -test_rewrite "npx vitest run" \ - "npx vitest run" \ - "rtk vitest run" +test_rewrite "npx vitest" \ + "npx vitest" \ + "rtk vitest" -test_rewrite "pnpm vitest run --coverage" \ - "pnpm vitest run --coverage" \ - "rtk vitest run --coverage" +test_rewrite "pnpm vitest --coverage" \ + "pnpm vitest --coverage" \ + "rtk vitest --coverage" echo "" # ---- SECTION 5: Should NOT rewrite ---- echo "--- Should NOT rewrite ---" -test_rewrite "already rtk" \ - "rtk git status" \ - "" - test_rewrite "heredoc" \ "cat <<'EOF' hello diff --git a/scripts/benchmark.sh b/scripts/benchmark.sh index a1e616bcc..15aee89df 100755 --- a/scripts/benchmark.sh +++ b/scripts/benchmark.sh @@ -367,7 +367,7 @@ if [ -f "package.json" ]; then fi if command -v vitest &> /dev/null || [ -f "node_modules/.bin/vitest" ]; then - bench "vitest run" "vitest run --reporter=json 2>&1 || true" "$RTK vitest run" + bench "vitest" "vitest run --reporter=json 2>&1 || true" "$RTK vitest" fi if command -v pnpm &> /dev/null; then diff --git a/src/cmds/js/vitest_cmd.rs b/src/cmds/js/vitest_cmd.rs index 4f5e9ae47..c4e089877 100644 --- a/src/cmds/js/vitest_cmd.rs +++ b/src/cmds/js/vitest_cmd.rs @@ -10,6 +10,7 @@ use crate::parser::{ emit_degradation_warning, emit_passthrough_warning, extract_json_object, truncate_passthrough, FormatMode, OutputParser, ParseResult, TestFailure, TestResult, TokenFormatter, }; +use crate::Commands; /// Vitest JSON output structures (tool-specific format) #[derive(Debug, Deserialize)] @@ -24,10 +25,6 @@ struct VitestJsonOutput { num_failed_tests: usize, #[serde(rename = "numPendingTests", default)] num_pending_tests: usize, - #[serde(rename = "startTime")] - start_time: Option, - #[serde(rename = "endTime")] - end_time: Option, } #[derive(Debug, Deserialize)] @@ -66,17 +63,13 @@ impl OutputParser for VitestParser { match json_result { Ok(json) => { let failures = extract_failures_from_json(&json); - let duration_ms = match (json.start_time, json.end_time) { - (Some(start), Some(end)) => Some(end.saturating_sub(start)), - _ => None, - }; let result = TestResult { total: json.num_total_tests, passed: json.num_passed_tests, failed: json.num_failed_tests, skipped: json.num_pending_tests, - duration_ms, + duration_ms: None, failures, }; @@ -210,31 +203,47 @@ fn extract_failures_regex(output: &str) -> Vec { failures } -#[derive(Debug, Clone)] -pub enum VitestCommand { - Run, -} - -pub fn run(cmd: VitestCommand, args: &[String], verbose: u8) -> Result { - match cmd { - VitestCommand::Run => run_vitest(args, verbose), - } -} - -fn run_vitest(args: &[String], verbose: u8) -> Result { +pub fn run_test(command: &Commands, args: &[String], verbose: u8) -> Result { let timer = tracking::TimedExecution::start(); - let mut cmd = package_manager_exec("vitest"); - cmd.arg("run"); // Force non-watch mode - - // Add JSON reporter for structured output - cmd.arg("--reporter=json"); + let (framework, mut cmd) = match command { + Commands::Vitest { .. } => { + let framework = "vitest"; + let mut cmd = package_manager_exec(framework); + cmd + // Force non-watch mode + .arg("run") + // Enable JSON structured output + .arg("--reporter=json"); + (framework, cmd) + } + Commands::Jest { .. } => { + let framework = "jest"; + let mut cmd = package_manager_exec(framework); + cmd + // Force non-watch mode + .arg("--no-watch") + // Enable JSON structured output + .arg("--json"); + (framework, cmd) + } + _ => unreachable!(), + }; for arg in args { + if arg == "run" + || arg.starts_with("--json") + || arg.starts_with("--reporter") + || arg.starts_with("--watch") + { + continue; + } cmd.arg(arg); } - let output = cmd.output().context("Failed to run vitest")?; + let output = cmd + .output() + .context(format!("Failed to run {}", framework))?; let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); let combined = format!("{}{}", stdout, stderr); @@ -246,30 +255,37 @@ fn run_vitest(args: &[String], verbose: u8) -> Result { let filtered = match parse_result { ParseResult::Full(data) => { if verbose > 0 { - eprintln!("vitest run (Tier 1: Full JSON parse)"); + eprintln!("{} run (Tier 1: Full JSON parse)", framework); } data.format(mode) } ParseResult::Degraded(data, warnings) => { if verbose > 0 { - emit_degradation_warning("vitest", &warnings.join(", ")); + emit_degradation_warning(framework, &warnings.join(", ")); } data.format(mode) } ParseResult::Passthrough(raw) => { - emit_passthrough_warning("vitest", "All parsing tiers failed"); + emit_passthrough_warning(framework, "All parsing tiers failed"); raw } }; - let exit_code = crate::core::utils::exit_code_from_output(&output, "vitest"); - if let Some(hint) = crate::core::tee::tee_and_hint(&combined, "vitest_run", exit_code) { + let exit_code = crate::core::utils::exit_code_from_output(&output, framework); + if let Some(hint) = + crate::core::tee::tee_and_hint(&combined, format!("{}_run", framework).as_str(), exit_code) + { println!("{}\n{}", filtered, hint); } else { println!("{}", filtered); } - timer.track("vitest run", "rtk vitest run", &combined, &filtered); + timer.track( + format!("{} run", framework).as_str(), + format!("rtk {} run", framework).as_str(), + &combined, + &filtered, + ); if !output.status.success() { return Ok(exit_code); @@ -289,8 +305,7 @@ mod tests { "numFailedTests": 0, "numPendingTests": 0, "testResults": [], - "startTime": 1000, - "endTime": 1450 + "startTime": 1000 }"#; let result = VitestParser::parse(json); @@ -301,7 +316,7 @@ mod tests { assert_eq!(data.total, 13); assert_eq!(data.passed, 13); assert_eq!(data.failed, 0); - assert_eq!(data.duration_ms, Some(450)); + assert_eq!(data.duration_ms, None); } #[test] @@ -343,7 +358,7 @@ mod tests { Scope: all 6 workspace projects WARN deprecated inflight@1.0.6: This module is not supported -{"numTotalTests": 13, "numPassedTests": 13, "numFailedTests": 0, "numPendingTests": 0, "testResults": [], "startTime": 1000, "endTime": 1450} +{"numTotalTests": 13, "numPassedTests": 13, "numFailedTests": 0, "numPendingTests": 0, "testResults": [], "startTime": 1000} "#; let result = VitestParser::parse(input); assert_eq!(result.tier(), 1, "Should succeed with Tier 1 (full parse)"); @@ -360,7 +375,7 @@ Scope: all 6 workspace projects let input = r#"[dotenv] Loading environment variables from .env [dotenv] Injected 5 variables -{"numTotalTests": 5, "numPassedTests": 4, "numFailedTests": 1, "numPendingTests": 0, "testResults": [], "startTime": 2000, "endTime": 2300} +{"numTotalTests": 5, "numPassedTests": 4, "numFailedTests": 1, "numPendingTests": 0, "testResults": [], "startTime": 2000} "#; let result = VitestParser::parse(input); assert_eq!(result.tier(), 1, "Should succeed with Tier 1 (full parse)"); @@ -370,13 +385,13 @@ Scope: all 6 workspace projects assert_eq!(data.total, 5); assert_eq!(data.passed, 4); assert_eq!(data.failed, 1); - assert_eq!(data.duration_ms, Some(300)); + assert_eq!(data.duration_ms, None); } #[test] fn test_vitest_parser_with_nested_json() { let input = r#"prefix text -{"numTotalTests": 2, "numPassedTests": 2, "numFailedTests": 0, "numPendingTests": 0, "testResults": [{"name": "test.js", "assertionResults": [{"fullName": "nested test", "status": "passed", "failureMessages": []}]}], "startTime": 1000, "endTime": 1100} +{"numTotalTests": 2, "numPassedTests": 2, "numFailedTests": 0, "numPendingTests": 0, "testResults": [{"name": "test.js", "assertionResults": [{"fullName": "nested test", "status": "passed", "failureMessages": []}]}], "startTime": 1000} "#; let result = VitestParser::parse(input); assert_eq!(result.tier(), 1, "Should succeed with Tier 1 (full parse)"); diff --git a/src/discover/registry.rs b/src/discover/registry.rs index 401cba9ba..0005cd21e 100644 --- a/src/discover/registry.rs +++ b/src/discover/registry.rs @@ -1029,19 +1029,32 @@ mod tests { } #[test] - fn test_rewrite_npx_tsc() { - assert_eq!( - rewrite_command("npx tsc --noEmit", &[]), - Some("rtk tsc --noEmit".into()) - ); - } - - #[test] - fn test_rewrite_pnpm_tsc() { - assert_eq!( - rewrite_command("pnpm tsc --noEmit", &[]), - Some("rtk tsc --noEmit".into()) - ); + fn test_rewrite_tsc() { + let commands = vec![ + "npm exec tsc", + "npm rum tsc", + "npm run tsc", + "npm run-script tsc", + "npm urn tsc", + "npm x tsc", + "pnpm dlx tsc", + "pnpm exec tsc", + "pnpm run tsc", + "pnpm run-script tsc", + "npm tsc", + "npx tsc", + "pnpm tsc", + "pnpx tsc", + "tsc", + ]; + for command in commands { + assert_eq!( + rewrite_command(&format!("{command} --noEmit"), &[]), + Some("rtk tsc --noEmit".into()), + "Failed for command: {}", + command + ); + } } #[test] @@ -1081,19 +1094,61 @@ mod tests { } #[test] - fn test_rewrite_npx_playwright() { - assert_eq!( - rewrite_command("npx playwright test", &[]), - Some("rtk playwright test".into()) - ); + fn test_rewrite_playwright() { + let commands = vec![ + "npm exec playwright", + "npm rum playwright", + "npm run playwright", + "npm run-script playwright", + "npm urn playwright", + "npm x playwright", + "pnpm dlx playwright", + "pnpm exec playwright", + "pnpm run playwright", + "pnpm run-script playwright", + "npm playwright", + "npx playwright", + "pnpm playwright", + "pnpx playwright", + "playwright", + ]; + for command in commands { + assert_eq!( + rewrite_command(&format!("{command} test"), &[]), + Some("rtk playwright test".into()), + "Failed for command: {}", + command + ); + } } #[test] fn test_rewrite_next_build() { - assert_eq!( - rewrite_command("next build --turbo", &[]), - Some("rtk next --turbo".into()) - ); + let commands = vec![ + "npm exec next build", + "npm rum next build", + "npm run next build", + "npm run-script next build", + "npm urn next build", + "npm x next build", + "pnpm dlx next build", + "pnpm exec next build", + "pnpm run next build", + "pnpm run-script next build", + "npm next build", + "npx next build", + "pnpm next build", + "pnpx next build", + "next build", + ]; + for command in commands { + assert_eq!( + rewrite_command(&format!("{command} --turbo"), &[]), + Some("rtk next --turbo".into()), + "Failed for command: {}", + command + ); + } } #[test] @@ -1918,67 +1973,427 @@ mod tests { // --- JS/TS tooling --- #[test] - fn test_classify_vitest() { - assert!(matches!( - classify_command("vitest run"), - Classification::Supported { - rtk_equivalent: "rtk vitest", - .. - } - )); + fn test_classify_lint() { + let commands = vec![ + "npm exec biome", + "npm exec eslint", + "npm rum biome", + "npm rum eslint", + "npm rum lint", + "npm run biome", + "npm run eslint", + "npm run lint", + "npm run-script biome", + "npm run-script eslint", + "npm run-script lint", + "npm urn biome", + "npm urn eslint", + "npm urn lint", + "npm x biome", + "npm x eslint", + "pnpm dlx biome", + "pnpm dlx eslint", + "pnpm exec biome", + "pnpm exec eslint", + "pnpm run biome", + "pnpm run eslint", + "pnpm run lint", + "pnpm run-script biome", + "pnpm run-script eslint", + "pnpm run-script lint", + "npm biome", + "npm eslint", + "npm lint", + "npx biome", + "npx eslint", + "npx lint", + "pnpm biome", + "pnpm eslint", + "pnpm lint", + "pnpx biome", + "pnpx eslint", + "pnpx lint", + "biome", + "eslint", + "lint", + ]; + for command in commands { + assert!( + matches!( + classify_command(command), + Classification::Supported { + rtk_equivalent: "rtk lint", + .. + } + ), + "Failed for command: {}", + command + ); + } } #[test] - fn test_rewrite_vitest() { - assert_eq!( - rewrite_command("vitest run", &[]), - Some("rtk vitest run".into()) - ); + fn test_rewrite_lint() { + let commands = vec![ + "npm exec biome", + "npm exec eslint", + "npm rum biome", + "npm rum eslint", + "npm rum lint", + "npm run biome", + "npm run eslint", + "npm run lint", + "npm run-script biome", + "npm run-script eslint", + "npm run-script lint", + "npm urn biome", + "npm urn eslint", + "npm urn lint", + "npm x biome", + "npm x eslint", + "pnpm dlx biome", + "pnpm dlx eslint", + "pnpm exec biome", + "pnpm exec eslint", + "pnpm run biome", + "pnpm run eslint", + "pnpm run lint", + "pnpm run-script biome", + "pnpm run-script eslint", + "pnpm run-script lint", + "npm biome", + "npm eslint", + "npm lint", + "npx biome", + "npx eslint", + "npx lint", + "pnpm biome", + "pnpm eslint", + "pnpm lint", + "pnpx biome", + "pnpx eslint", + "pnpx lint", + "biome", + "eslint", + "lint", + ]; + for command in commands { + assert_eq!( + rewrite_command(command, &[]), + Some("rtk lint".into()), + "Failed for command: {}", + command + ); + } } #[test] - fn test_rewrite_pnpm_vitest() { - assert_eq!( - rewrite_command("pnpm vitest run", &[]), - Some("rtk vitest run".into()) - ); + fn test_classify_jest() { + let commands = vec![ + "jest run", + "jest", + "npm exec jest run", + "npm exec jest", + "npm jest run", + "npm jest", + "npm rum jest run", + "npm rum jest", + "npm run jest run", + "npm run jest", + "npm run-script jest run", + "npm run-script jest", + "npm urn jest run", + "npm urn jest", + "npm x jest run", + "npm x jest", + "npx jest run", + "npx jest", + "pnpm dlx jest run", + "pnpm dlx jest", + "pnpm exec jest run", + "pnpm exec jest", + "pnpm jest run", + "pnpm jest", + "pnpm run jest run", + "pnpm run jest", + "pnpm run-script jest run", + "pnpm run-script jest", + "pnpx jest run", + "pnpx jest", + ]; + for command in commands { + assert!( + matches!( + classify_command(command), + Classification::Supported { + rtk_equivalent: "rtk jest", + .. + } + ), + "Failed for command: {}", + command + ); + } + } + + #[test] + fn test_rewrite_jest() { + let commands = vec![ + "jest run", + "jest", + "npm exec jest run", + "npm exec jest", + "npm jest run", + "npm jest", + "npm rum jest run", + "npm rum jest", + "npm run jest run", + "npm run jest", + "npm run-script jest run", + "npm run-script jest", + "npm urn jest run", + "npm urn jest", + "npm x jest run", + "npm x jest", + "npx jest run", + "npx jest", + "pnpm dlx jest run", + "pnpm dlx jest", + "pnpm exec jest run", + "pnpm exec jest", + "pnpm jest run", + "pnpm jest", + "pnpm run jest run", + "pnpm run jest", + "pnpm run-script jest run", + "pnpm run-script jest", + "pnpx jest run", + "pnpx jest", + ]; + for command in commands { + assert_eq!( + rewrite_command(command, &[]), + Some("rtk jest".into()), + "Failed for command: {}", + command + ); + } + } + + #[test] + fn test_classify_vitest() { + let commands = vec![ + "npm exec vitest run", + "npm exec vitest", + "npm rum vitest run", + "npm rum vitest", + "npm run vitest run", + "npm run vitest", + "npm run-script vitest run", + "npm run-script vitest", + "npm urn vitest run", + "npm urn vitest", + "npm vitest run", + "npm vitest", + "npm x vitest run", + "npm x vitest", + "npx vitest run", + "npx vitest", + "pnpm dlx vitest run", + "pnpm dlx vitest", + "pnpm exec vitest run", + "pnpm exec vitest", + "pnpm run vitest run", + "pnpm run vitest", + "pnpm run-script vitest run", + "pnpm run-script vitest", + "pnpm vitest run", + "pnpm vitest", + "pnpx vitest run", + "pnpx vitest", + "vitest run", + "vitest", + ]; + for command in commands { + assert!( + matches!( + classify_command(command), + Classification::Supported { + rtk_equivalent: "rtk vitest", + .. + } + ), + "Failed for command: {}", + command + ); + } + } + + #[test] + fn test_rewrite_vitest() { + let commands = vec![ + "npm exec vitest run", + "npm exec vitest", + "npm rum vitest run", + "npm rum vitest", + "npm run vitest run", + "npm run vitest", + "npm run-script vitest run", + "npm run-script vitest", + "npm urn vitest run", + "npm urn vitest", + "npm vitest run", + "npm vitest", + "npm x vitest run", + "npm x vitest", + "npx vitest run", + "npx vitest", + "pnpm dlx vitest run", + "pnpm dlx vitest", + "pnpm exec vitest run", + "pnpm exec vitest", + "pnpm run vitest run", + "pnpm run vitest", + "pnpm run-script vitest run", + "pnpm run-script vitest", + "pnpm vitest run", + "pnpm vitest", + "pnpx vitest run", + "pnpx vitest", + "vitest run", + "vitest", + ]; + for command in commands { + assert_eq!( + rewrite_command(command, &[]), + Some("rtk vitest".into()), + "Failed for command: {}", + command + ); + } } #[test] fn test_classify_prisma() { - assert!(matches!( - classify_command("npx prisma migrate dev"), - Classification::Supported { - rtk_equivalent: "rtk prisma", - .. - } - )); + let commands = vec![ + "npm exec prisma", + "npm rum prisma", + "npm run prisma", + "npm run-script prisma", + "npm urn prisma", + "npm x prisma", + "pnpm dlx prisma", + "pnpm exec prisma", + "pnpm run prisma", + "pnpm run-script prisma", + "npm prisma", + "npx prisma", + "pnpm prisma", + "pnpx prisma", + "prisma", + ]; + for command in commands { + assert!( + matches!( + classify_command(format!("{command} migrate dev").as_str()), + Classification::Supported { + rtk_equivalent: "rtk prisma", + .. + } + ), + "Failed for command: {}", + command + ); + } } #[test] fn test_rewrite_prisma() { - assert_eq!( - rewrite_command("npx prisma migrate dev", &[]), - Some("rtk prisma migrate dev".into()) - ); + let commands = vec![ + "npm exec prisma", + "npm rum prisma", + "npm run prisma", + "npm run-script prisma", + "npm urn prisma", + "npm x prisma", + "pnpm dlx prisma", + "pnpm exec prisma", + "pnpm run prisma", + "pnpm run-script prisma", + "npm prisma", + "npx prisma", + "pnpm prisma", + "pnpx prisma", + "prisma", + ]; + for command in commands { + assert_eq!( + rewrite_command(format!("{command} migrate dev").as_str(), &[]), + Some("rtk prisma migrate dev".into()), + "Failed for command: {}", + command + ); + } } #[test] fn test_rewrite_prettier() { - assert_eq!( - rewrite_command("npx prettier --check src/", &[]), - Some("rtk prettier --check src/".into()) - ); + let commands = vec![ + "npm exec prettier", + "npm rum prettier", + "npm run prettier", + "npm run-script prettier", + "npm urn prettier", + "npm x prettier", + "pnpm dlx prettier", + "pnpm exec prettier", + "pnpm run prettier", + "pnpm run-script prettier", + "npm prettier", + "npx prettier", + "pnpm prettier", + "pnpx prettier", + "prettier", + ]; + for command in commands { + assert_eq!( + rewrite_command(format!("{command} --check src/").as_str(), &[]), + Some("rtk prettier --check src/".into()), + "Failed for command: {}", + command + ); + } } #[test] - fn test_rewrite_pnpm_list() { + fn test_rewrite_pnpm_command() { + let commands = vec![ + "exec", + "i", + "install", + "list", + "ls", + "outdated", + "run", + "run-script", + ]; + for command in commands { + assert_eq!( + rewrite_command(format!("pnpm {command}").as_str(), &[]), + Some(format!("rtk pnpm {command}")), + "Failed for command: pnpm {}", + command + ); + } + } + + #[test] + fn test_rewrite_npx() { assert_eq!( - rewrite_command("pnpm list", &[]), - Some("rtk pnpm list".into()) + rewrite_command("npx svgo", &[]), + Some("rtk npx svgo".to_string()), ); } - // --- Compound operator edge cases --- #[test] diff --git a/src/discover/rules.rs b/src/discover/rules.rs index b315edd77..0e357d7bb 100644 --- a/src/discover/rules.rs +++ b/src/discover/rules.rs @@ -44,7 +44,7 @@ pub const RULES: &[RtkRule] = &[ subcmd_status: &[("fmt", RtkStatus::Passthrough)], }, RtkRule { - pattern: r"^pnpm\s+(list|ls|outdated|install)", + pattern: r"^pnpm\s+(exec|i|install|list|ls|outdated|run|run-script)", rtk_cmd: "rtk pnpm", rewrite_prefixes: &["pnpm"], category: "PackageManager", @@ -53,7 +53,7 @@ pub const RULES: &[RtkRule] = &[ subcmd_status: &[], }, RtkRule { - pattern: r"^npm\s+(run|exec)", + pattern: r"^npm\s+(exec|run|run-script|rum|urn|x)\s+", rtk_cmd: "rtk npm", rewrite_prefixes: &["npm"], category: "PackageManager", @@ -107,24 +107,75 @@ pub const RULES: &[RtkRule] = &[ subcmd_status: &[], }, RtkRule { - pattern: r"^(npx\s+|pnpm\s+)?tsc(\s|$)", + pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?tsc(\s|$)", rtk_cmd: "rtk tsc", - rewrite_prefixes: &["pnpm tsc", "npx tsc", "tsc"], + rewrite_prefixes: &[ + "npm exec tsc", + "npm rum tsc", + "npm run tsc", + "npm run-script tsc", + "npm tsc", + "npm urn tsc", + "npm x tsc", + "npx tsc", + "pnpm dlx tsc", + "pnpm exec tsc", + "pnpm run tsc", + "pnpm run-script tsc", + "pnpm tsc", + "pnpx tsc", + "tsc", + ], category: "Build", savings_pct: 83.0, subcmd_savings: &[], subcmd_status: &[], }, RtkRule { - pattern: r"^(npx\s+|pnpm\s+)?(eslint|biome|lint)(\s|$)", + pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?(biome|eslint|lint)(\s|$)", rtk_cmd: "rtk lint", rewrite_prefixes: &[ - "npx eslint", - "pnpm lint", - "npx biome", - "eslint", "biome", + "eslint", "lint", + "npm biome", + "npm eslint", + "npm exec biome", + "npm exec eslint", + "npm lint", + "npm rum biome", + "npm rum eslint", + "npm rum lint", + "npm run biome", + "npm run eslint", + "npm run lint", + "npm run-script biome", + "npm run-script eslint", + "npm run-script lint", + "npm urn biome", + "npm urn eslint", + "npm urn lint", + "npm x biome", + "npm x eslint", + "npx biome", + "npx eslint", + "npx lint", + "pnpm biome", + "pnpm dlx biome", + "pnpm dlx eslint", + "pnpm eslint", + "pnpm exec biome", + "pnpm exec eslint", + "pnpm lint", + "pnpm run biome", + "pnpm run eslint", + "pnpm run lint", + "pnpm run-script biome", + "pnpm run-script eslint", + "pnpm run-script lint", + "pnpx biome", + "pnpx eslint", + "pnpx lint", ], category: "Build", savings_pct: 84.0, @@ -132,45 +183,180 @@ pub const RULES: &[RtkRule] = &[ subcmd_status: &[], }, RtkRule { - pattern: r"^(npx\s+|pnpm\s+)?prettier", + pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?prettier", rtk_cmd: "rtk prettier", - rewrite_prefixes: &["npx prettier", "pnpm prettier", "prettier"], + rewrite_prefixes: &[ + "npm exec prettier", + "npm prettier", + "npm rum prettier", + "npm run prettier", + "npm run-script prettier", + "npm urn prettier", + "npm x prettier", + "npx prettier", + "pnpm dlx prettier", + "pnpm exec prettier", + "pnpm prettier", + "pnpm run prettier", + "pnpm run-script prettier", + "pnpx prettier", + "prettier", + ], category: "Build", savings_pct: 70.0, subcmd_savings: &[], subcmd_status: &[], }, RtkRule { - pattern: r"^(npx\s+|pnpm\s+)?next\s+build", + pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?next\s+build", rtk_cmd: "rtk next", - rewrite_prefixes: &["npx next build", "pnpm next build", "next build"], + rewrite_prefixes: &[ + "next build", + "npm exec next build", + "npm next build", + "npm rum next build", + "npm run next build", + "npm run-script next build", + "npm urn next build", + "npm x next build", + "npx next build", + "pnpm dlx next build", + "pnpm exec next build", + "pnpm next build", + "pnpm run next build", + "pnpm run-script next build", + "pnpx next build", + ], category: "Build", savings_pct: 87.0, subcmd_savings: &[], subcmd_status: &[], }, RtkRule { - pattern: r"^(pnpm\s+|npx\s+)?(vitest|jest|test)(\s|$)", + pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?jest(\s+run)?(\s|$)", + rtk_cmd: "rtk jest", + rewrite_prefixes: &[ + "jest run", + "jest", + "npm exec jest run", + "npm exec jest", + "npm jest run", + "npm jest", + "npm rum jest run", + "npm rum jest", + "npm run jest run", + "npm run jest", + "npm run-script jest run", + "npm run-script jest", + "npm urn jest run", + "npm urn jest", + "npm x jest run", + "npm x jest", + "npx jest run", + "npx jest", + "pnpm dlx jest run", + "pnpm dlx jest", + "pnpm exec jest run", + "pnpm exec jest", + "pnpm jest run", + "pnpm jest", + "pnpm run jest run", + "pnpm run jest", + "pnpm run-script jest run", + "pnpm run-script jest", + "pnpx jest run", + "pnpx jest", + ], + category: "Tests", + savings_pct: 99.0, + subcmd_savings: &[], + subcmd_status: &[], + }, + RtkRule { + pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?vitest(\s+run)?(\s|$)", rtk_cmd: "rtk vitest", - rewrite_prefixes: &["pnpm vitest", "npx vitest", "vitest", "jest"], + rewrite_prefixes: &[ + "npm exec vitest run", + "npm exec vitest", + "npm rum vitest run", + "npm rum vitest", + "npm run vitest run", + "npm run vitest", + "npm run-script vitest run", + "npm run-script vitest", + "npm urn vitest run", + "npm urn vitest", + "npm vitest run", + "npm vitest", + "npm x vitest run", + "npm x vitest", + "npx vitest run", + "npx vitest", + "pnpm dlx vitest run", + "pnpm dlx vitest", + "pnpm exec vitest run", + "pnpm exec vitest", + "pnpm run vitest run", + "pnpm run vitest", + "pnpm run-script vitest run", + "pnpm run-script vitest", + "pnpm vitest run", + "pnpm vitest", + "pnpx vitest run", + "pnpx vitest", + "vitest run", + "vitest", + ], category: "Tests", savings_pct: 99.0, subcmd_savings: &[], subcmd_status: &[], }, RtkRule { - pattern: r"^(npx\s+|pnpm\s+)?playwright", + pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?playwright", rtk_cmd: "rtk playwright", - rewrite_prefixes: &["npx playwright", "pnpm playwright", "playwright"], + rewrite_prefixes: &[ + "npm exec playwright", + "npm playwright", + "npm rum playwright", + "npm run playwright", + "npm run-script playwright", + "npm urn playwright", + "npm x playwright", + "npx playwright", + "playwright", + "pnpm dlx playwright", + "pnpm exec playwright", + "pnpm playwright", + "pnpm run playwright", + "pnpm run-script playwright", + "pnpx playwright", + ], category: "Tests", savings_pct: 94.0, subcmd_savings: &[], subcmd_status: &[], }, RtkRule { - pattern: r"^(npx\s+|pnpm\s+)?prisma", + pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?prisma", rtk_cmd: "rtk prisma", - rewrite_prefixes: &["npx prisma", "pnpm prisma", "prisma"], + rewrite_prefixes: &[ + "npm exec prisma", + "npm prisma", + "npm rum prisma", + "npm run prisma", + "npm run-script prisma", + "npm urn prisma", + "npm x prisma", + "npx prisma", + "pnpm dlx prisma", + "pnpm exec prisma", + "pnpm prisma", + "pnpm run prisma", + "pnpm run-script prisma", + "pnpx prisma", + "prisma", + ], category: "Build", savings_pct: 88.0, subcmd_savings: &[], diff --git a/src/hooks/init.rs b/src/hooks/init.rs index 42a3db7ca..6edba7ca6 100644 --- a/src/hooks/init.rs +++ b/src/hooks/init.rs @@ -109,11 +109,16 @@ rtk prettier --check # Files needing format only (70%) rtk next build # Next.js build with route metrics (87%) ``` -### Test (90-99% savings) +### Test (60-99% savings) ```bash rtk cargo test # Cargo test failures only (90%) -rtk vitest run # Vitest failures only (99.5%) +rtk go test # Go test failures only (90%) +rtk jest # Jest failures only (99.5%) +rtk vitest # Vitest failures only (99.5%) rtk playwright test # Playwright failures only (94%) +rtk pytest # Python test failures only (90%) +rtk rake test # Ruby test failures only (90%) +rtk rspec # RSpec test failures only (60%) rtk test # Generic test wrapper - failures only ``` diff --git a/src/main.rs b/src/main.rs index 11332034a..9c732422f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,7 +70,7 @@ struct Cli { skip_env: bool, } -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum Commands { /// List directory contents with token-optimized output (proxy to native ls) Ls { @@ -440,10 +440,18 @@ enum Commands { create: bool, }, + /// Jest commands with compact output + Jest { + /// Additional jest arguments + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, + /// Vitest commands with compact output Vitest { - #[command(subcommand)] - command: VitestCommands, + /// Additional vitest arguments + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, }, /// Prisma commands with compact output (no ASCII art) @@ -693,7 +701,7 @@ enum Commands { }, } -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum HookCommands { /// Process Gemini CLI BeforeTool hook (reads JSON from stdin) Gemini, @@ -701,7 +709,7 @@ enum HookCommands { Copilot, } -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum GitCommands { /// Condensed diff output Diff { @@ -782,7 +790,7 @@ enum GitCommands { Other(Vec), } -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum PnpmCommands { /// List installed packages (ultra-dense) List { @@ -818,7 +826,7 @@ enum PnpmCommands { Other(Vec), } -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum DockerCommands { /// List running containers Ps, @@ -836,7 +844,7 @@ enum DockerCommands { Other(Vec), } -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum ComposeCommands { /// List compose services (compact) Ps, @@ -855,7 +863,7 @@ enum ComposeCommands { Other(Vec), } -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum KubectlCommands { /// List pods Pods { @@ -884,17 +892,7 @@ enum KubectlCommands { Other(Vec), } -#[derive(Subcommand)] -enum VitestCommands { - /// Run tests with filtered output (90% token reduction) - Run { - /// Additional vitest arguments - #[arg(trailing_var_arg = true, allow_hyphen_values = true)] - args: Vec, - }, -} - -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum PrismaCommands { /// Generate Prisma Client (strip ASCII art) Generate { @@ -915,7 +913,7 @@ enum PrismaCommands { }, } -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum PrismaMigrateCommands { /// Create and apply migration Dev { @@ -940,7 +938,7 @@ enum PrismaMigrateCommands { }, } -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum CargoCommands { /// Build with compact output (strip Compiling lines, keep errors) Build { @@ -983,7 +981,7 @@ enum CargoCommands { Other(Vec), } -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum DotnetCommands { /// Build with compact output Build { @@ -1010,7 +1008,7 @@ enum DotnetCommands { Other(Vec), } -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum GoCommands { /// Run tests with compact output (90% token reduction via JSON streaming) Test { @@ -1162,7 +1160,7 @@ fn run_fallback(parse_error: clap::Error) -> Result { } } -#[derive(Subcommand)] +#[derive(Debug, Subcommand)] enum GtCommands { /// Compact stack log output Log { @@ -1785,11 +1783,9 @@ fn run_cli() -> Result { 0 } - Commands::Vitest { command } => match command { - VitestCommands::Run { args } => { - vitest_cmd::run(vitest_cmd::VitestCommand::Run, &args, cli.verbose)? - } - }, + Commands::Jest { ref args } | Commands::Vitest { ref args } => { + vitest_cmd::run_test(&cli.command, args, cli.verbose)? + } Commands::Prisma { command } => match command { PrismaCommands::Generate { args } => {