Repository navigation
Conversation
📊 Automated PR Analysis
SummaryAdds a new Review Checklist
Linked issues: #4239 Analyzed automatically by wshm · This is an automated analysis, not a human review. |
KuSh
left a comment
There was a problem hiding this comment.
Round 1 of 3 — CHANGES REQUESTED
Claim: rtk ng build (and a rewrite of bare ng build) compacts Angular build-table padding and the leading progress pair, keeps every bundle and diagnostic, preserves argv, exit code and stderr, and passes help, verbose, watch and uncertain workspaces through.
Scope: accept (round 1, frozen)
Ran: real Angular 22.2.3 app built in Docker (Node 22): default production build, 4 lazy routes, budget warning, budget error, TypeScript error, template error, -c development, --progress=false, --stats-json, --source-map, ng build app, --help, --watch=false, app + ng g library workspace, watch: true in a configuration, outside a workspace, ng missing from PATH, SIGTERM mid-build, NG_FORCE_TTY=1 with closed stdin. 20 rows, native ng build against rtk rewrite "<cmd>" and then the rewritten command, stdout and stderr captured separately. Invariants: under the hook | split streams | every decision (mutation run below) | signal relay. Savings 28.2% on the default build, 28.7% with lazy routes, 22.7% with --progress=false, against the 20% floor (CONTRIBUTING.md).
Pins: 50 mutations over the decisions the PR adds: 29 killed, 21 survived the full suite. 3 have no observable effect (-h and -v, which Angular rejects as Unknown argument, and watch inside an array). The other 18 are grouped below.
Matrix: run this round. Pin check: run this round.
CI @48b0a4a1b: all green. GitHub reports a merge conflict with develop.
Blocks merge (3, frozen at round 1)
The behaviour is correct on every real build I ran: every stdout token is kept apart from the progress pair, stderr is byte-identical, exit codes match native, rtk recall returns the full output, and a configured watch stays live. What blocks is that 18 of the decisions behind that result can be changed with the whole suite still green. Each item below lists its surviving mutations and the rows a test has to assert. Each mutation must turn the new test red.
-
src/cmds/js/ng_cmd.rs:112— unpinned behaviour: which workspaces are captured (8 mutations). This guard decides between buffering the build and streaming it live, so it is the decision that matters most if it is wrong..all(→.any(inknown_builders. Row: a workspace with an app plus a library generated byng g library(@angular/build:ng-packagr).ng build appmust pass through byte-identical; it does today.- Remove
@angular-devkit/build-angular:application,:browser-esbuildor:browserfrom the list (3 mutations). Row: one workspace per builder name, each compacted.:applicationis the default builder for Angular 17–19 projects. - Drop
.or_else(|| project.get("targets")). Row: a project usingtargetsinstead ofarchitect, compacted. - Drop
!projects.is_empty() &&. Row:"projects": {}(whatng new --create-application=falsewrites), passed through. Err(_) => return true→false. Row: anangular.jsonthat exists but cannot be read (for example, a directory), passed through.current_dir().map_or(true, …)→map_or(false, …). Row: rtk started in a directory that has since been removed, passed through.
-
src/cmds/js/ng_cmd.rs:185— unpinned behaviour: which lines count as table rows (6 mutations). Each guard below can be removed without any test failing. Each needs one input that only this guard rejects (or accepts), with the exact output asserted:- The header lookahead in
filter_buildmade always true. Row: a header line followed by a non-row line; both must stay byte-identical. - Drop
|| cells[0].ends_with(".mjs"). Row: a.mjsrow, compacted. - Drop
&& !cells[1].is_empty(). Row: a row with an empty Names cell, kept verbatim. - Size check made always true. Row:
main.js | main | hugeafter a header, kept verbatim. - Drop
|-fromSIZE. Row: a row with a-size cell, compacted. - Remove the CRLF progress prefix. Row: CRLF output, progress pair stripped.
None of my real builds produced a
.mjsrow, a-size or CRLF output. If Angular never prints them, removing those arms closes the item as well as a test would. You know the output better than I do, so your call. - The header lookahead in
-
unpinned behaviour: process and registration (4 mutations).
src/core/stream.rs:727:cmd.stdin(Stdio::inherit())→Stdio::null(). Row: a fakengthat echoes one line it reads from stdin;printf 'y\n' | rtk ng buildmust showy.ng_cmd.rs:52: thefiltered != result.stdoutguard on the hint and thenever_worsecall at:58each back up the other, so neither can be killed alone. Row: a failing compiler-only build with tee enabled (the integration tests setRTK_TEE=0); stdout must be byte-identical with no[full outputline. The test must fail when both are removed together.src/core/tracking.rs:550: remove"ng"fromSUBCOMMAND_ROUTERS. Row: the stored label forrtk ng build appkeepsbuild, as the sibling router tests check.
Optional — will not hold merge
ng build -c developmentgets 0% (349 → 349 bytes). Angular ends each row of the development table with an empty cell (main.js | main | 1.39 MB |), socells.len() != columnsrejects every row and the header lookahead fails. Accepting one empty trailing cell would compact development builds too.- A workspace that contains a library passes
ng build appthrough untouched (455 → 455 bytes), becauseknown_builderschecks every project rather than the one being built. This is safe, but common monorepos get nothing from the filter. tee_and_hintwrites a recovery entry on every failing build, including compiler-only failures where the hint is then thrown away. Calling it only whenfiltered != result.stdoutavoids that.ngis not inRUST_HANDLED_COMMANDS(src/core/toml_filter.rs:266), so a user TOML filter onnggets no shadow warning. Several existing commands are missing from that list too; #3770 tracks it.- The
handvshort arms inshould_filter: Angular rejectsng build -handng build -v(Error: Unknown argument: h, exit 1), and that error prints the same captured or not. - The new sentence in the main README intro reads as a one-feature announcement; the
what-rtk-covers.mdrow already covers it.
Checked and correct — no need to re-verify
- Every stdout token kept apart from the progress pair, and stderr byte-identical, on 12 real builds (success, lazy, development, budget warning and error, TypeScript and template errors,
--progress=false,--stats-json,--source-map,ng build app). - Exit codes match native on every row, including 1 on budget and compiler errors.
- The recovery hint on the budget-error build (
rtk recall <id>) is left alone byrtk rewriteand returns the full stdout and stderr, identical to native apart from build timing. --help,--watch=false, a configuredwatch: true(live output undertimeout), and running from outside a workspace all pass through.- SIGTERM mid-build: rtk prints what it captured, exits like native, and leaves no
nodeoresbuildprocess behind. NG_FORCE_TTY=1with stdin closed: same 418 bytes and exit 127 as native, no hang.- Rewrite:
ng buildpositive, pluscd … && ng build,CI=1 ng build,ng build 2>&1,--configuration=production; negative forng b,ng build | tail,ng build>out.txt. - Merged onto current
develop(thestream.rsconflict is two adjacent additions; keeping both resolves it): fmt and clippy clean,cargo test --all4995 passed (one machine-local failure that also fails ondevelop).
Your questions
- "The CLI-testing rules still mention a 60% blanket release floor … Maintainer feedback on that discrepancy and the value of this limited filter would be useful" → The floor is 20% of bash output (CONTRIBUTING.md); the 60% in
.claude/rules/cli-testing.mdis stale and does not apply. The filter is worth merging as claimed: 28% on real default builds, and diagnostic-only failures left alone is the right call.
Next
Merge develop (in the src/core/stream.rs conflict keep both functions), then add tests that kill the 18 surviving mutations above, or remove the arms in item 2 that Angular never reaches. After that, this merges.
Rounds: 1/3. Threads: 3 open (waiting on the author), 0 resolved this round.
| return false; | ||
| }; | ||
| !projects.is_empty() | ||
| && projects.values().all(|project| { |
There was a problem hiding this comment.
Blocks merge, item 1: which workspaces get captured. Changing .all( to .any( here, removing any one of the three @angular-devkit/build-angular:* names, dropping the targets fallback, dropping !projects.is_empty(), Err(_) => return false in workspace_may_watch, or current_dir().map_or(false, …) in run: each change leaves the whole suite green. The review body lists the row each test must assert. The first is a real ng g library workspace, where ng build app must pass through byte-identical.
| } | ||
| } | ||
|
|
||
| fn is_row(line: &str, columns: usize) -> bool { |
There was a problem hiding this comment.
Blocks merge, item 2: which lines count as rows. The header lookahead in filter_build, the .mjs suffix, !cells[1].is_empty(), the SIZE check, the - alternative in SIZE, and the CRLF progress prefix can each be removed with the suite still green. Each needs an input only that guard decides. Removing an arm that Angular never reaches closes it as well as a test would.
| /// final newline and does not cap either stream. The caller must emit the result, | ||
| /// flush its output, then call [`die_by_relayed_signal`], as the shared runner does. | ||
| pub fn exec_capture_stdin_with_relay(cmd: &mut Command) -> Result<CaptureResult> { | ||
| cmd.stdin(Stdio::inherit()) |
There was a problem hiding this comment.
Blocks merge, item 3: Stdio::inherit() → Stdio::null() here passes every test. A fake ng that echoes one line read from stdin would pin it. The same item covers the hint guard plus never_worse pair (ng_cmd.rs:52/58), which only fails when both are removed, and "ng" in SUBCOMMAND_ROUTERS (tracking.rs:550).
80b6c1b to
15cb602
Compare
Summary
Refs #4239. This PR covers the first, bounded part of the request: native
rtk ng buildand a narrow rewrite of bareng build. It does not close the broader issue or overlap with the Karma work in #2541.ngexecutable resolved from PATH, argv, exit status and complete stderr, including budget warnings; no package-manager fallback or injected Angular flagsTest, serve, wrappers and path-based executable rewrites remain outside this initial filter.
Real-output measurements
Generated and built an actual local Angular application with the official Angular CLI/framework/build 22.2.1, Node 24.19.0, npm 11.9.0 and TypeScript 6.0.3. The checked-in fixtures are raw captures, including separate stdout/stderr and recorded build help.
Using RTK's production
ceil(bytes / 4)estimator on native-vs-RTK builds:These are command-output estimates, not actual tokenizer counts or billing savings. Angular's current output is already compact, so this change deliberately makes modest savings and leaves diagnostic-only failures unchanged.
The successful fixtures satisfy the current contribution guide's 20% threshold. The CLI-testing rules still mention a 60% blanket release floor; this implementation does not meet the 60% target. Maintainer feedback on that discrepancy and the value of this limited filter would be useful during review.
Verification
cargo fmt --all --checkcargo clippy --all-targets -- -D warningscargo test --all: 4,112 passed, 8 ignoredcargo test --all -- --ignored: 8 passedGIT_DIR,RTK_NO_TOML=1,RTK_TEE=0and--include-ignored: 4,120 passed--watch, passthrough streaming, SIGTERM flushing/no orphan, recovery, uncapped warnings, exact output accounting, malformed output and narrow rewrite boundariesLinux was exercised here. Windows/macOS runtime validation and broader Angular version coverage are not claimed. Native fixture EOF blank lines are intentional and preserved byte-for-byte.