feat(npm): apply biome filter when package.json script invokes biome#1492
feat(npm): apply biome filter when package.json script invokes biome#1492LukaPrebil wants to merge 2 commits intortk-ai:developfrom
Conversation
`npm run lint`, `npm lint`, `npm [rum|urn|x] lint`, and `npx lint` previously rewrote to `rtk lint`, which defaults to running ESLint via `npx eslint`. On Biome projects the JSON parse fails silently with exit 0, hiding real lint failures from agents. Route these through `rtk npm` / `rtk npx` instead - the generic npm filter is script-agnostic and preserves biome's real output and exit code. Mirrors rtk-ai#678's pattern for the pnpm side. Scope: - new early-return block in rewrite_segment_inner handles the npm/npx `lint` variants with env prefix and redirect suffix preserved - `split_npm_dispatch` + `match_lint_script` helpers keep the block compact - npm rule pattern extended with `|lint` + `(\s|$)` suffix so bare `npm lint` still classifies as `rtk npm` - `npm lint` / `npm run lint` / `npm run-script lint` / `npm rum|urn lint` / `npx lint` removed from lint rule's rewrite_prefixes - 12 new unit tests (incl. env-prefix, redirect, word-boundary regression against `npm lint-staged`, and asserts that bare biome / eslint still route to rtk lint) - `test_classify_lint` intentionally unchanged: classify still picks the lint rule (last-match wins), the rewrite now short-circuits to rtk npm/npx. Documented classify/rewrite drift. Out of scope (filed separately): - biome check routing through the biome TOML filter - rtk lint exit-0 on auto-defaulted ESLint JSON parse failure - package.json-aware filter dispatch for npm run scripts
Stacks on rtk-ai#1491. After that PR, `npm run lint` on a Biome project no longer triggers the ESLint JSON-parse bug, but the generic filter_npm_output leaves biome's `Checked N files` chrome in place. This PR teaches `rtk npm run <script>` to peek at the script body in package.json and chain the corresponding tool filter when the tool is unambiguous: - new src/cmds/js/script_detect.rs with a tempfile-backed test suite: detect_script_tool / detect_script_tool_in, conservative token-level parsing (returns None for mixed-tool scripts, substring matches, unknown bodies, malformed JSON) - wire detection in npm_cmd::run: when the script is biome-only, chain filter_npm_output -> toml_filter::apply_filter(biome). Everything else keeps the filter_npm_output-only path (correctness-preserving fallback) - extend filter_npm_output to also strip the bare `> <script-body>` banner npm emits before running the script. Previously we only stripped the `> pkg@ver <script>` variant. Both lines are npm's own echoes; downstream filters (e.g. biome.toml) only recognise bare tool output and need the npm chrome gone to reach their on_empty fallback - 18 new detector tests + 3 filter-chain tests Scope is biome-only by design. Adding eslint/prettier/tsc detection is additive: a new ScriptTool variant plus a filter hookup per tool. Tracked as a follow-up issue.
Smoke test (PR 1 alone vs PR 1 + PR 2)Scratch Clean pass
Error pass
ESLint project (detector returns
|
📊 Automated PR Analysis
SummaryTeaches Review Checklist
Analyzed automatically by wshm · This is an automated analysis, not a human review. |
| "pnpm eslint", | ||
| "pnpm exec biome", | ||
| "pnpm exec eslint", | ||
| "pnpm lint", |
There was a problem hiding this comment.
You can't leave pnpm/pnpx alone, it sure have the same problem
| fn detect_tool_in_script(body: &str) -> Option<ScriptTool> { | ||
| let has_biome = has_tool_token(body, "biome"); | ||
| let has_eslint = has_tool_token(body, "eslint"); | ||
| let has_prettier = has_tool_token(body, "prettier"); | ||
| let has_tsc = has_tool_token(body, "tsc"); | ||
|
|
||
| let tools_found = [has_biome, has_eslint, has_prettier, has_tsc] | ||
| .iter() | ||
| .filter(|b| **b) | ||
| .count(); | ||
| if tools_found != 1 { | ||
| return None; | ||
| } | ||
|
|
||
| if has_biome { | ||
| Some(ScriptTool::Biome) | ||
| } else { | ||
| // Recognised but no filter wired yet (eslint / prettier / tsc). | ||
| None | ||
| } | ||
| } |
There was a problem hiding this comment.
This doesn't seem to be required. We can directly call toml_filter::find_matching_filter on it
| match script_name.and_then(detect_script_tool) { | ||
| Some(ScriptTool::Biome) => { | ||
| // Biome TOML filter matches `^biome\b`. | ||
| if let Some(biome) = toml_filter::find_matching_filter("biome") { |
There was a problem hiding this comment.
You're not respecting the RTK_NO_TOML env variable that disable toml filters.
Also this should not just call toml filters, it should also check recursively if the command match any rtk natively handled command (that's not something we allow ATM), if vitest or jest are behind the lint script we should call rtk vitest/rtk jest
Summary
npm run linton a Biome project no longer triggers the ESLint JSON-parse bug, but the genericfilter_npm_outputleaves biome'sChecked N fileschrome in place. This PR teachesrtk npm run <script>to peek at the script body inpackage.jsonand chain the biome TOML filter when the script unambiguously invokes biome.src/cmds/js/script_detect.rswith tempfile-backed tests (noset_current_dirraces):detect_script_tool/detect_script_tool_in, conservative token-level parsing. ReturnsNonefor mixed-tool scripts, substring matches, unknown bodies, and malformed JSON - no silent misclassification.filter_npm_outputto strip the bare> <script-body>banner npm echoes before handing off. Both npm banner lines are now stripped uniformly; downstream filters see clean tool output.Test plan
cargo fmt --all && cargo clippy --all-targets && cargo testrtk <command>output inspected