github.com-vercel-turborepo
all · 12 devs · built 2026-06-13
Repository snapshot
Monthly reports
Highlights
- Introduced comprehensive *Rsbuild* support with new example applications and updated documentation [e5334232 · Néstor].
- Added a new *heap allocation profiling* capability for the Rust CLI to identify and validate memory optimizations [c7ad6f29 · Anthony Shew].
- Enhanced *Turborepo* configuration parsing to allow object-based `experimentalCI` settings for greater flexibility [6f662f23 · Anthony Shew].
- Implemented extensive *test suite optimizations* to improve performance and stability, particularly on Windows CI, by trimming redundant tests and skipping unnecessary installations (e.g., [8e094b8e · Anthony Shew], [62973fec · Anthony Shew], [34e89b24 · Anthony Shew]).
- Fixed a critical bug in `turbo prune` to correctly respect *root gitignore* rules, preventing unwanted files in Docker builds [f96ccc4d · Anthony Shew].
- Improved *profile tracing coverage* for `turbo run` by adding spans for remote cache resolution and signal handler shutdown [3063672e · Anthony Shew].
Observations
- The *waste score* surged by +228% (32 current vs 10 5-month average), indicating a significant amount of rework or discarded changes during May 2026.
- The *grow score* decreased by -78% (2 current vs 10 5-month average) and the *maintenance score* dropped by -42% (7 current vs 13 5-month average), suggesting a reduced focus on new feature development and proactive maintenance compared to the 5-month average.
- Total output (grow + maintenance) decreased by -61% (9 current vs 23 5-month average), despite a stable commit volume (+6% with 244 commits vs 230 5-month average).
- The *Bun lockfile pruning logic* in `turborepo-lockfiles` was a significant area of rework, with a fix [7952b462 · Anthony Shew] being reverted [3b1b6e96 · Anthony Shew] and then re-implemented [8d4eaf81 · Anthony Shew], and another fix [a77a0e53 · Anthony Shew] later in the month, indicating persistent challenges in this component.
- Multiple critical bug fixes were implemented for *process management* and *shutdown behavior*, including graceful shutdown on Windows [ac6f3627 · Anthony Shew], hanging PTY tasks [52e81bd9 · Anthony Shew], and persistent task `stdin` handling [26ae68ba · Anthony Shew] within the `turborepo-process` crate.
- Several fixes addressed issues in *OpenTelemetry instrumentation*, including hardening endpoint validation [076ff973 · Anthony Shew] and using build-scale duration buckets [6ed6fb04 · Anthony Shew] in `turborepo-otel`.
- A substantial number of commits (at least 11 identified) were dedicated to *test infrastructure improvements* and *optimizations*, primarily focused on reducing CI execution time and improving test stability, especially for Windows environments.
- The `release PR auto-merge` mechanism required a fix [155e672b · Anthony Shew] to restore its functionality, indicating a temporary disruption in the automated release workflow.
Performance over time
ETV stacked by Growth, Maintenance and Fixes — 90-day moving average, normalized to ETV / month.
Average performance per developer
ETV per active developer per month — 30-day moving average.
Active developers over time
Unique developers committing each day — 90-day moving average.
Knowledge concentration
How dependent is this repo on a small number of contributors? Higher top-1 share = higher key-person risk.
Anthony Shew owns 87.9 % of commits.
Top contributors
Most impactful commits
Top 20 by ETV in the all-time window.
- 2.9ETVchore: Add `turborepo-log` crate to improve our logging situation (#12285) ## Summary - Introduces `turborepo-log`, a new crate for structured user-facing log events (warnings, errors, informational output), deliberately separate from `tracing` which remains for developer diagnostics. - The global `Logger` dispatches events to pluggable `LogSink` implementations. Ships with two built-in sinks: `CollectorSink` (in-memory buffer for post-run summaries) and `FileSink` (newline-delimited JSON with optional size limiting). - All user-supplied strings are sanitized against ANSI escape sequences and control characters to prevent terminal injection. ## Why Today, user-facing messages in `turbo run` are scattered across ad-hoc `println!`/`eprintln!` calls and tightly coupled to `turborepo-ui`'s terminal rendering. This makes it hard to capture warnings for post-run summaries, write them to structured log files, or route them to the TUI without touching rendering code. `turborepo-log` sits at the bottom of the dependency graph (no dependency on `turborepo-ui`) and provides a clean boundary: subsystems emit structured events, sinks decide how to present or store them. A future terminal sink in `turborepo-ui` can implement `LogSink` to bridge into the existing rendering pipeline. ## Testing ```sh cargo test -p turborepo-log ``` The crate includes unit tests for all public types and an integration test (`tests/global_init.rs`) that exercises the full global logger lifecycle including lazy handle resolution.Anthony Shew · e9434ca4 · 2026-03-13
- 2.7ETVrefactor: Split process child module (#13014) ## Why The process child module mixed public process management, platform-specific handle internals, IO, shutdown semantics, and tests in one large file. Splitting those responsibilities reduces context needed to review future process changes. ## What Moves child handle internals, IO helpers, shutdown types and processing, state manager/channel logic, PTY test guard, and tests into sibling modules under `crates/turborepo-process/src/child/` while preserving the public API from `child.rs`. ## How This is intended as a mechanical refactor. Verified with `cargo fmt -p turborepo-process`, targeted child tests, `cargo test -p turborepo-process -- --test-threads=1`, `git diff --check`, and the repository pre-push hook.Anthony Shew · 24dd7c1f · 2026-06-03
- 2.6ETVdocs: layout redesign (#10178) ### Description Newly formatted pages for docs content. ### Testing Instructions There are a great many changes in here that probably aren't useful to review as file changes. You'll want to open the preview and make sure all the design holds together.Anthony Shew · ae3db3d1 · 2025-03-21
- 2.6ETVfix: Hardening for daemon IPC endpoints (#12742) - Prevent cross-user access to local daemon RPCs by moving daemon IPC into per-user paths and enforcing owner-only socket permissions. - Reject unauthorized Unix peers and validate Windows socket ownership before clients trust a daemon endpoint.Anthony Shew · 13a9a8b1 · 2026-05-07
- 2.3ETVfix: Preserve graceful shutdown output (#12607) ## Summary - Keep signal-driven `turbo run` cleanup alive until task shutdown finishes so TUI and grouped output are not torn down early or truncated. - Route shutdown and cache-flush status through the logging pipeline, distinguish signal vs close shutdowns, and avoid signal-only force timers on normal completion. - Harden process shutdown and parent-death cleanup while adding regression coverage for TUI, process, and signal-handling edge cases.Anthony Shew · 0b5bb210 · 2026-04-13
- 2.0ETVchore: Extract turbo_json module to turborepo-turbo-json crate (#11344) ## Summary Extract ~5,400 lines of turbo.json parsing, validation, and processing code from `turborepo-lib` into a new `turborepo-turbo-json` crate. This continues the modularization effort to make `turborepo-lib` a thin orchestration layer. ## What's extracted | File | Lines | Purpose | |------|-------|---------| | `lib.rs` | ~300 | Main `TurboJson` type, constants, core API | | `raw.rs` | ~320 | Raw parsed types (`RawTurboJson`, `RawTaskDefinition`, etc.) | | `parser.rs` | ~340 | Biome-based JSON parsing | | `processed.rs` | ~620 | Processed task definitions with DSL token handling | | `extend.rs` | ~610 | Task inheritance/extension logic | | `validator.rs` | ~250 | turbo.json validation rules | | `future_flags.rs` | ~40 | Feature flags | | `error.rs` | ~320 | Error types with miette diagnostics | ## What stays in turborepo-lib - `TurboJsonLoader` - Depends on MFE (microfrontends) and task_access - `TurboJsonReader` - Reading turbo.json from disk - `TaskDefinitionFromProcessed` trait - Creates `TaskDefinition` from processed types - Re-exports for backward compatibility ## Verification - ✅ All 339 `turborepo-lib` tests pass - ✅ All 57 `turborepo-turbo-json` tests pass - ✅ All 72 engine tests pass - ✅ Binary tested with `examples/basic` - ✅ **Zero behavioral changes** for CLI users (error messages, validation, parsing all identical) ## Test Plan ```bash cargo test -p turborepo-turbo-json cargo test -p turborepo-lib cargo build --bin turbo ```Anthony Shew · e9fef6e3 · 2025-12-30
- 1.9ETVfeat(turbo_json): add `$TURBO_EXTENDS$` (#10763) ### Description This PR adds a new DSL keyword for array task definitions that alters the behavior when using `extends` in a package `turbo.json`. Usually a field from a package task definition will completely override the root field e.g. Root: `{ "env": ["FOO"] }` Package: `{ "env": ["BAR"] }` Result: `{"env": ["BAR"] }` If the package uses the new `$TURBO_EXTENDS$` the root field value will be extended from instead of overridden. e.g. Root: `{ "env": ["FOO"] }` Package: `{ "env": ["$TURBO_EXTENDS$", "BAR"] }` Result: `{"env": ["FOO", "BAR"] }` This functionality allows for greater flexibility when specializing package task definitions without needing to copy/paste the shared values and remembering to update them if the root changed. It is currently gated by `turboExtends` feature flag in the root `turbo.json`. Reviewing this each commit individually in this PR is *highly* suggested. The actual feature add is all done in the final commit, the rest are prefactors. The primary prefactors are: - Plumbing through future flags to be read at config time, but usable at task resolution time. This is necessary since this is set at root and needs to be known when reading task definitions from package `turbo.json`s - Adding a new intermediate step for task definition creation named "processed". Removes a majority of the DSL concepts and validations that are exclusive to a single field. This paves the way for future extensions to the DSL. Nerd note: Technically, we could extend `$TURBO_EXTENDS$` to additional fields that are monoids. Extension behavior is really: ``` let a = if extends { base } else { mempty } a mappend b ``` The only interesting addition we could make would be around boolean fields which has 2 different monoids: any and all. Which one to choose would depend on the default value of the flag. e.g. `cache` defaults to false so we would want an any (or) behavior. I am not suggesting we do this, it is more confusing than it is worthwhile. ### Testing Instructions Added a fair amount of unit tests for the whole task definition resolution steps.Chris Olszewski · 6ce04db4 · 2025-08-21
- 1.9ETVfix: Normalize CRLF line endings in file hashing to match git (#12572) ## Summary When `.gitattributes` marks files as `text` or `text=auto`, git normalizes CRLF→LF in blob objects. After `turbo prune` removes `.git/`, the manual hashing path was hashing raw bytes (with CRLF), producing different hashes than the git path. This caused cache misses when running `turbo run` in pruned output. This PR adds a `crlf` module to `turborepo-scm` that replicates git's CRLF→LF normalization so turbo's file hashes match git's regardless of whether the git or manual (no-git) code path is used. ## What changed - **New `crlf` module** (`crates/turborepo-scm/src/crlf.rs`): Parses `.gitattributes` via `gix-attributes`, resolves `text`/`text=auto`/`-text`/`binary` attributes per-file, and normalizes CRLF→LF during hashing. Uses a `BlobHasher` trait to write the normalization algorithm once across both the gix and sha1 hasher implementations. Single-pass for the common case (no CRLFs), two-pass only when normalization is actually needed. - **`turbo prune` preserves `.gitattributes`**: Added to `ADDITIONAL_FILES` so the manual hashing path in pruned output has the same normalization context. - **`.gitattributes` in global hash**: Changing normalization rules now invalidates all caches. - **Consolidated duplicate code**: Removed parallel `hash_file` (hash_object.rs) and `git_like_hash_file` (manual.rs) implementations. Both paths now route through the unified `BlobHasher` abstraction in `crlf.rs`, eliminating duplicate blob-header construction and file-type validation. Extracted `resolve_or_load` helper to replace 3x copy-pasted attrs-resolution boilerplate. - **CI comments updated**: Clarified that CRLF normalization is driven by `.gitattributes`, not `core.autocrlf`. ## Known limitations - Only root `.gitattributes` is loaded (nested per-directory files are not consulted) - `eol=` attribute is not handled - `core.autocrlf` from git config is not read; normalization is exclusively `.gitattributes`-driven ## Testing The test suite covers: - Ground-truth validation against `git hash-object --path` (with filters) - Cross-implementation agreement between gix and sha1 hashers - Chunk-boundary edge cases (CRLF split across 64KB buffer boundary) - Binary detection (NUL in first 8KB) - Simulated `turbo prune` → manual hash → verify hashes match git path - Dirty file re-hashing matches committed blob OID - `core.autocrlf=true` combined with `text=auto` - Package-scoped `.gitattributes` patterns Fixes #9616 Fixes #5081Anthony Shew · 1403c9ee · 2026-04-06
- 1.8ETVfeat: Task-level `extends` field (#11259) ## What? Adds task-level `extends` field to Package Configuration task definitions, allowing workspaces to exclude specific tasks from inheritance. ## Why? Enables workspaces to opt-out of inherited tasks from root/parent configs. Previously, if root defined `lint`, all workspaces inherited it. Now workspaces can exclude tasks they don't need. ## How? - `"extends": false` (no other config) → task excluded entirely for workspace - `"extends": false` (with other config) → fresh task definition, no inheritance - `extends` disallowed in root `turbo.json` (validation error added) - Exclusions propagate through extends chain - Modified engine/builder.rs to track exclusion state through inheritance resolution - Added `validate_no_task_extends_in_root` validator - Updated JSON schema with `extends` boolean field ## Testing instructions Added testing all around this functionality and have manually tested many situations to see how the API feels.Anthony Shew · 9a9033b3 · 2025-12-15
- 1.8ETVfix: Prevent cache archive symlink reads (#12813) ## Summary - Prevent cache archive creation from reading through symlink/reparse-point races before archiving task outputs. - On Unix, resolve archive inputs with fd-relative traversal so symlinked parent directories and final-component symlink swaps are rejected before reads. - On Windows, open final files with reparse-point no-follow behavior and verify the opened handle still resolves under the cache anchor before reading. Linear: TURBO-5574Anthony Shew · ab90c81e · 2026-05-16
- 1.8ETVrefactor: Split CLI module (#13013) ## Why The CLI module had grown large enough to make command parsing and execution flow hard to review in one file. Splitting type definitions and tests away from the runtime flow keeps the entrypoint focused. ## What Moves clap argument and command type definitions into `cli/args.rs`, moves CLI tests into `cli/test.rs`, and leaves `cli/mod.rs` focused on command resolution, telemetry/version setup, and execution. ## How This is intended as a mechanical refactor. Verified with `cargo fmt -p turborepo-lib`, `cargo check -p turborepo-lib`, `cargo test -p turborepo-lib cli::test`, `cargo test -p turborepo-lib`, `git diff --check`, and the repository pre-push hook.Anthony Shew · 7bd88416 · 2026-06-03
- 1.7ETVfeat: Turborepo Devtools (#11263) ### Description A new command for visualizing your Package Graphs and Task Graphs: `turbo devtools` ### Testing Instructions Working with it locally feels pretty great. Wrote some tests, as well. --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>Anthony Shew · 10b9099e · 2025-12-17
- 1.7ETVdocs: Geistdocs update (#11807) Partial Geistdocs update. --------- Co-authored-by: Anthony Shew <anthony.shew@vercel.com>Hayden Bleasel · 91d0e7bb · 2026-02-12
- 1.7ETVchore: Move engine builder tests from `turborepo-lib` to `turborepo-engine` (#11353) ## Summary Move 3,196 lines of EngineBuilder integration tests from `turborepo-lib` to `turborepo-engine` where the production code lives. - Reduces `turborepo-lib` by ~3,200 lines - Tests are now colocated with the code they test - Part of ongoing modularization effort ## Testing - All 62 `turborepo-engine` tests pass - All 255 `turborepo-lib` tests passAnthony Shew · 48fb739c · 2025-12-31
- 1.7ETVfeat: Add experimental structured logging with `--json` and `--log-file` flags (#12405) ## Summary - Adds `--json` flag to stream NDJSON to stdout and `--log-file [path]` flag to write structured JSON logs to a file, both behind an experimental marker - Supports `TURBO_LOG_FILE` environment variable as an alternative to the CLI flag - Deliberately omits `logFile` from `turbo.json` — structured logging is a per-invocation concern better suited to CLI flags and env vars than checked-in config ## How to test ```bash # NDJSON to stdout turbo run build --json 2>/dev/null | head -5 # File output (default path) turbo run build --log-file cat .turbo/logs/*.json | python3 -m json.tool | head -20 # File output (custom path) turbo run build --log-file=my-log.json # Env var TURBO_LOG_FILE=1 turbo run build # Both modes simultaneously turbo run build --json --log-file=build.json ``` Each JSON entry has `timestamp`, `source`, `level`, and `text` fields. ANSI escape sequences are stripped in file output.Anthony Shew · 7ca06013 · 2026-03-20
- 1.6ETVfix: Harden token protection with `secrecy` crate and close exposure gaps (#11831) ## Summary - Migrate `SecretString` to use the `secrecy` crate for zeroize-on-drop - Convert `VerifiedSsoUser` and `VerificationResponse` token fields from `String` to `SecretString` - Change `get_token_with_refresh()` return type from `Option<String>` to `Option<SecretString>` - Replace manual `bearer_header()` with reqwest's built-in `bearer_auth()` which marks headers as sensitive ## Why The initial `SecretString` implementation had three gaps: 1. **`VerifiedSsoUser.token` and `VerificationResponse.token` were plain `String`s.** Custom `Debug` impls redacted them, but `format!("{}", user.token)` or accidental serialization could still leak. Now they use `SecretString`, and the manual `Debug` impls are replaced by derives. 2. **`get_token_with_refresh()` returned `Option<String>`.** It called `.expose().to_string()` on 4 code paths, creating unprotected `String` values that callers had to re-wrap. The token now stays as `SecretString` through the entire refresh flow. 3. **No zeroize-on-drop.** The custom wrapper left secrets in freed memory. Backing it with `secrecy::SecretBox<str>` ensures secrets are scrubbed on drop. Additionally, the manual `bearer_header()` helper used `format!("Bearer {token}")` without marking the header as sensitive. reqwest's `bearer_auth()` does this automatically via `HeaderValue::set_sensitive(true)`, preventing auth headers from appearing in tracing middleware output. <sub>CLOSES TURBO-5203</sub>Anthony Shew · 200e4c38 · 2026-02-13
- 1.6ETVfeat: Add `affectedUsingTaskInputs` future flag for task-level `--affected` detection (#12247) ## Summary - Adds `affectedUsingTaskInputs` future flag that changes `--affected` from package-level to task-level granularity - When enabled, only tasks whose declared `inputs` globs match changed files are selected (plus transitive dependents), instead of running all tasks in changed packages - Extracts shared glob matching into `turborepo-types/task_input_matching` for reuse between `turbo run --affected` and `turbo query { affectedTasks }` ## Design The task-level filtering is a post-build step on the single engine, not a separate code path. The normal `--affected` scope resolution determines affected packages and builds the engine as usual. If the future flag is enabled, `Engine::retain_affected_tasks` then filters the engine in place to only the tasks whose inputs match changed files plus their transitive dependents. This means: - One code path (no branching into a separate engine build) - One engine (mutated in place via `retain_affected_tasks`) - `--parallel` naturally works because the filter is re-applied after the parallel engine rebuild ## How it works 1. Normal `--affected` scope resolution determines affected packages 2. Engine is built with those packages (same as before) 3. SCM is queried for changed files between base and head refs 4. Each task's `inputs` globs are checked against changed files, with a compilation cache per unique (package, inputs) pair 5. `Engine::retain_affected_tasks` prunes the engine to only affected tasks + their transitive dependents via reversed DFS Global changes (root `package.json`, `turbo.json`, lockfile, or user-configured `globalDependencies`) short-circuit to running all tasks. ## Testing To verify, enable the flag in `turbo.json` and use tasks with specific `inputs` configurations: ```json { "tasks": { "build": { "dependsOn": ["^build"] }, "test": { "inputs": ["$TURBO_DEFAULT$", "!**/*.md"] }, "typecheck": { "inputs": ["$TURBO_DEFAULT$", "!**/*.md", "!**/*.test.ts"] } }, "futureFlags": { "affectedUsingTaskInputs": true } } ``` Changing a `.md` file should only trigger `build` (not `test` or `typecheck`). Changing a `.test.ts` should trigger `build` and `test` (not `typecheck`). Unit tests cover both `retain_affected_tasks` (8 tests) and `affected_task_ids` (10 tests). Integration tests cover the end-to-end `turbo run --affected --dry=json` path (5 tests). ## Known limitations - The `turbo query { affectedTasks }` path pre-filters files to the package directory, missing cross-package `$TURBO_ROOT$` inputs. TODO added in code for follow-up. --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>Anthony Shew · 36a5a6ae · 2026-03-12
- 1.6ETVchore: update to Rust 1.89.0 (#10749) ### Description <!-- ✍️ Write a short summary of your work. If necessary, include relevant screenshots. --> - fix mismatched_lifetime_syntaxes lints - let chains are now stable in edition 2024, so use them - change workspace logic so "2024" edition is now applied for all package with `workspace = true` ### Testing Instructions CI <!-- Give a quick description of steps to test your changes. -->Maksim Bondarenkov · 8113d43b · 2025-08-13
- 1.6ETVtest: Add turbo watch tests and refactor file change classification (#12119) ## Summary - Extract `classify_changed_files` as a pure, synchronous function from the async Subscriber polling loop, making the core file-classification logic directly unit-testable without tokio, broadcast channels, or file watchers - Add `MapperFailed` variant to `FileChangeAction` enum so callers can distinguish mapper errors from actual config changes and log at appropriate severity - Deduplicate the rediscovery+reinit pattern (previously copy-pasted 3x) via `rediscover_and_reinit()` + macro - Move `filter_to_watched` earlier in the watch pipeline (before `stop_impacted_tasks`) to avoid unnecessary task-graph traversal for unwatched packages - Add comprehensive unit tests for `classify_changed_files`, `ChangedPackages`, `handle_change_event`, and the full `PackageChangesWatcher` pipeline - Add integration tests for `turbo watch build` using marker files as side-effect detectors ## Why `turbo watch` had zero test coverage. The file-classification logic was embedded deep in an async loop, making it impossible to unit test without spinning up the full daemon infrastructure. The refactor separates decisions from effects, enabling thorough testing of all classification paths (config changes, gitignored files, .git directory filtering, package changes, mapper failures). ## Key changes for reviewers **Production code:** - `package_changes_watcher.rs`: The `classify_changed_files` function is the extracted pure core. `FileChangeAction::MapperFailed` replaces the previous behavior of silently returning `ConfigChanged` on mapper errors. The `expect()` on file watcher paths is replaced with graceful skip+log. `warn!("hashes are the same")` downgraded to `debug!` (expected behavior, not a warning). - `watch.rs`: `filter_to_watched` doc explains why `All` is unfiltered. Comment at call site explains the safety invariant (engine only contains watched-package tasks). **Test infrastructure:** - `ClassifyFixture` struct eliminates ~15 lines of repeated setup per classify test - `WatchGuard` RAII struct ensures child processes are cleaned up even on test panic - Negative assertion tests use a sentinel event pattern instead of timing-dependent 500ms timeouts - `nextest.toml` filter uses regex anchor `/^watch_/` to prevent accidental matches **Integration tests (`watch_test.rs`):** - `watch_initial_run_executes_tasks` — verifies both packages build on startup - `watch_file_change_reruns_affected_package` — verifies file change triggers rebuild (requires git commit because hash watcher uses git-based hashing) - `watch_clean_shutdown_on_sigint` — verifies process exits cleanly on SIGINT (unix only)Anthony Shew · b0deeb7a · 2026-03-02
- 1.5ETVfix: Recover Vercel auth tokens across login flows (#12631) - Recover rejected or expired Vercel tokens across `turbo login`, `turbo sso`, `turbo link`, and remote-cache retry paths instead of surfacing raw auth failures or reusing bad tokens - Upgrade reusable legacy/config-backed Vercel tokens into Turbo OAuth state when possible, but fall back to a fresh login when Vercel rejects token exchange for login flows - Reduce duplicate recovery warnings and add regression coverage for refresh, backfill, exchange, and forbidden-response branchesAnthony Shew · 0d83a518 · 2026-04-16