Repository navigation
feat(run): add --dry-run to preview cache hits - #792
lifeiscontent wants to merge 9 commits into
Conversation
`vp run --dry-run <task>` plans the run as usual and then reports, for each task, whether the local cache would hit, why it would miss, or that caching is disabled. A task that runs after something that isn't a hit is reported as unknown, since that earlier task could change its inputs. Nothing runs and nothing is written: the cache database is opened read-only (or skipped if it doesn't exist), the local lookup no longer records the execution key itself, and the last run summary is left alone. The remote cache is not queried.
main added an uploads field to ExecutionCache, and the extra dry run branch in main_inner put it over clippy's line limit, so the reporter builder for the log mode moves into its own function.
# Conflicts: # CHANGELOG.md
Upstream voidzero-dev#821 changed the run summary to also show the count of successful tasks. The real runs in the dry_run fixture now print that count.
# Conflicts: # CHANGELOG.md
|
Thanks for this. We'd like to use
We have both implemented with snapshot tests against this design (same read-only open and the same association move), and can rebase them onto your branch. Let us know which you'd prefer. |
|
@jasonkuhrt thanks, both additions sound useful. Please send them as a follow-up PR once this one lands. The maintainers have asked to keep PRs small so they can review and merge them one at a time. Treating dependents of a restoring command as |
# Conflicts: # CHANGELOG.md
|
@lifeiscontent is there anything I can do to help get this one merged? |
|
@jasonkuhrt yeah, get me a job at cloud flare haha 😆 |
There was no way to ask whether a run would hit the cache, or what it would run, without running it (#791, vite-plus#2848). `vp run --dry-run` plans the run and checks the cache as a real run does, the remote cache included, and prints each command's line and the summary. It runs nothing and records nothing. The cache is opened as in a real run, but no entry is written, a hit's outputs aren't downloaded or restored, and `last-summary.json` isn't written, so `--last-details` still shows the last real run. - Each line is a real run's, without `, executing` or `, replaying`, and with no blank line between commands. `--log` is ignored, since it only shapes task output. - The summary follows a real run's rules, with "nothing was run" in place of the successful count and saved time, and no `--last-details` hint. The detailed summary (`-v`) has no exit icons. - A cache lookup error is printed under its command, and stops the dry run as it stops a real run. Otherwise a dry run exits 0 once planning succeeds. - A nested `vp run --dry-run` isn't inlined: it's started as a process, which prints its own dry run, so an enclosing dry run doesn't start it. - Without a task, `--dry-run` is an error, like `vp run -v`. A dry run goes through the same lookup and spawn path as a real run, which skips each step that would write or run: recording a local hit, downloading a remote one, replaying it, and spawning the command. This supersedes #792, whose write-free lookup and test scenario it builds on. Co-authored-by: Aaron Reisman <aaron@lifeiscontent.net> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was no way to ask whether a run would hit the cache, or what it would run, without running it (#791, vite-plus#2848). `vp run --dry-run` plans the run and checks the cache as a real run does, the remote cache included, and prints each command's line and the summary. It runs nothing and records nothing. The cache is opened as in a real run, but no entry is written, a hit's outputs aren't downloaded or restored, and `last-summary.json` isn't written, so `--last-details` still shows the last real run. - Each line is a real run's, without `, executing` or `, replaying`, and with no blank line between commands. `--log` is ignored, since it only shapes task output. - The summary follows a real run's rules, with "nothing was run" in place of the successful count and saved time, and no `--last-details` hint. The detailed summary (`-v`) has no exit icons. - A cache lookup error is printed under its command, and stops the dry run as it stops a real run. Otherwise a dry run exits 0 once planning succeeds. - A nested `vp run --dry-run` isn't inlined: it's started as a process, which prints its own dry run, so an enclosing dry run doesn't start it. - Without a task, `--dry-run` is an error, like `vp run -v`. A dry run goes through the same lookup and spawn path as a real run, which skips each step that would write or run: recording a local hit, downloading a remote one, replaying it, and spawning the command. This supersedes #792, whose write-free lookup and test scenario it builds on. Co-authored-by: Aaron Reisman <aaron@lifeiscontent.net> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was no way to ask whether a run would hit the cache, or what it would run, without running it (#791, vite-plus#2848). `vp run --dry-run` plans the run and checks the cache as a real run does, the remote cache included, and prints each command's line and the summary. It runs nothing and records nothing. The cache is opened as in a real run, but no entry is written, a hit's outputs aren't downloaded or restored, and `last-summary.json` isn't written, so `--last-details` still shows the last real run. - Each line is a real run's, without `, executing` or `, replaying`, and with no blank line between commands. `--log` is ignored, since it only shapes task output: `--dry-run` and `--log` move from `RunFlags`, which selects tasks, to `RunCommand`, and resolve to a `RunMode` that is `Real { log }` or `Dry`, so a dry run carries no log mode. - The summary follows a real run's rules, with "nothing was run" in place of the successful count and saved time, and no `--last-details` hint. The detailed summary (`-v`) has no exit icons. - A cache lookup error is printed under its command, and stops the dry run as it stops a real run. Otherwise a dry run exits 0 once planning succeeds. - A nested `vp run --dry-run` isn't inlined: it's started as a process, which prints its own dry run, so an enclosing dry run doesn't start it. - Without a task, `--dry-run` is an error, like `vp run -v`. A dry run goes through the same lookup and spawn path as a real run, which skips each step that would write or run: recording a local hit, downloading a remote one, replaying it, and spawning the command. This supersedes #792, whose write-free lookup and test scenario it builds on. Co-authored-by: Aaron Reisman <aaron@lifeiscontent.net> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was no way to ask whether a run would hit the cache, or what it would run, without running it (#791, vite-plus#2848). `vp run --dry-run` plans the run and checks the cache as a real run does, the remote cache included, and prints each command's line and the summary. It runs nothing and records nothing. The cache is opened as in a real run, but no entry is written, a hit's outputs aren't downloaded or restored, and `last-summary.json` isn't written, so `--last-details` still shows the last real run. - Each line is a real run's, without `, executing` or `, replaying`, and with no blank line between commands. `--log` is ignored, since it only shapes task output: `--dry-run` and `--log` move from `RunFlags`, which selects tasks, to `RunCommand`, and resolve to a `RunMode` that is `Real { log }` or `Dry`, so a dry run carries no log mode. - The summary follows a real run's rules, with "nothing was run" in place of the successful count and saved time, and no `--last-details` hint. The detailed summary (`-v`) has no exit icons. - A cache lookup error is printed under its command, and stops the dry run as it stops a real run. Otherwise a dry run exits 0 once planning succeeds. - Both now exit 1 after a failed lookup: the summary reporter records only tasks that started, so a task that failed before it started didn't count, and the run exited 0. A shared flag now makes such a task fail the run. - A nested `vp run --dry-run` isn't inlined: it's started as a process, which prints its own dry run, so an enclosing dry run doesn't start it. - Without a task, `--dry-run` is an error, like `vp run -v`. A dry run goes through the same lookup and spawn path as a real run, which skips each step that would write or run: recording a local hit, downloading a remote one, replaying it, and spawning the command. This supersedes #792, whose write-free lookup and test scenario it builds on. Co-authored-by: Aaron Reisman <aaron@lifeiscontent.net> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was no way to ask whether a run would hit the cache, or what it would run, without running it (#791, vite-plus#2848). `vp run --dry-run` plans the run and checks the cache as a real run does, the remote cache included, and prints each command's line and the summary. It runs nothing and records nothing. The cache is opened as in a real run, but no entry is written, a hit's outputs aren't downloaded or restored, and `last-summary.json` isn't written, so `--last-details` still shows the last real run. - Each line is a real run's, without `, executing` or `, replaying`, and with no blank line between commands. `--log` is ignored, since it only shapes task output: `--dry-run` and `--log` move from `RunFlags`, which selects tasks, to `RunCommand`, and resolve to a `RunMode` that is `Real { log }` or `Dry`, so a dry run carries no log mode. - The summary follows a real run's rules, with "nothing was run" in place of the successful count and saved time, and no `--last-details` hint. The detailed summary (`-v`) has no exit icons. - A cache lookup error is printed under its command, and stops the dry run as it stops a real run. Otherwise a dry run exits 0 once planning succeeds. - Both now exit 1 after a failed lookup: the summary reporter records only tasks that started, so a task that failed before it started didn't count, and the run exited 0. A shared flag now makes such a task fail the run. - A nested `vp run --dry-run` isn't inlined: it's started as a process, which prints its own dry run, so an enclosing dry run doesn't start it. - Without a task, `--dry-run` is an error, like `vp run -v`. A dry run goes through the same lookup and spawn path as a real run, which skips each step that would write or run: recording a local hit, downloading a remote one, replaying it, and spawning the command. This supersedes #792, whose write-free lookup and test scenario it builds on. Co-authored-by: Aaron Reisman <aaron@lifeiscontent.net> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
## Motivation Before starting a long run, there's no way to see which tasks would come from the cache and which would run, or why one would miss, without running them (#791, voidzero-dev/vite-plus#2848). [The dry-run RFC](https://github.com/voidzero-dev/vite-task/blob/main/docs/rfcs/dry-run.md) specifies `vp run --dry-run` for this. This is the first PR of the stack that implements it: the text output. The PRs above it add every change of a miss (#961), `-v` (#963), `--json` (#966, #967), `unknown` (#968) and `-vv` (#969). This supersedes #792 by @lifeiscontent, whose write-free lookup and test scenario it builds on, so it credits them as co-author. Closes #791. ## Changes `vp run --dry-run` plans the run and checks the cache as a real run does, the remote cache included, then prints each command's line and the summary. It runs nothing and records nothing. The cache is opened as in a real run, but no entry is written, a hit's outputs aren't downloaded or restored, and `last-summary.json` isn't written, so `--last-details` still shows the last real run. ``` $ vp run --dry-run test $ vtt print-file src/a.txt ◉ cache hit $ vtt print tested --- vp run: 1/2 cache hit (50%), nothing was run. ``` - A dry run goes through the real run's lookup and spawn path, which skips each step that would write or run: recording a local hit, downloading a remote hit, replaying a hit, and spawning the command. - Each line is a real run's, without `, executing` or `, replaying`, and with no blank line between commands. `--log` is ignored, since it only shapes task output: `--dry-run` and `--log` move from `RunFlags`, which selects tasks, to `RunCommand`, and resolve to a `RunMode` of `Real { log }` or `Dry`, so a dry run can't carry a log mode. - The summary follows a real run's rules, with "nothing was run" in place of the successful count and the time saved, and without the `--last-details` hint. The detailed summary (`-v`) has no ✓ or exit codes. Real run, unchanged: ``` vp run: 1/2 cache hit (50%), 2/2 successful. (Run `vp run --last-details` for full details) │ │ │ │ │ │ │ │ │ └─ tasks │ │ │ └─ tasks that succeeded │ │ └─ cache hit rate │ └─ tasks └─ cache hits ``` Dry run: ``` vp run: 1/2 cache hit (50%), nothing was run. │ │ │ │ │ └─ cache hit rate │ └─ tasks └─ cache hits ``` - A cache lookup error is printed under its command and stops the dry run, as it stops a real run, and both exit 1. Real runs stopped but exited 0 before, since the summary only counted tasks that started; a task that fails before it starts now fails the run. Otherwise a dry run exits 0 once planning succeeds. - A nested `vp run --dry-run` isn't inlined. It's started as a process that prints its own dry run, so an enclosing dry run doesn't start it. - Without a task, `--dry-run` is an error, like `vp run -v`. After the task name, it's passed to the task, like any other flag. - RFC: the summary and cache-error rules are written down, and the sentence about the first changed file being the first in path order is removed. Without `-v`, the file check stops at whichever changed file it finds first. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches cache lookup, remote fetch, spawn, and exit-code paths across the runner; mistakes could skip real execution side effects or mis-report failures, though behavior is gated on `RunKind::Dry`. > > **Overview** > Adds **`vp run --dry-run`**, which plans the task graph and performs full cache lookups (including remote) but **does not** spawn processes, replay outputs, download remote archives, write cache entries, or persist `last-summary.json`. > > The CLI introduces **`RunMode`/`RunKind`**: `--dry-run` and `--log` move onto `RunCommand` (dry runs always use interleaved output). Execution and cache layers thread `RunKind` through lookup, spawn, and reporters so dry runs short-circuit after reporting cache status (`WouldSpawn` / `DryRun` not-updated reasons). > > **Output and summary** match a real run’s command lines and cache indicators, but omit `, executing` / `, replaying`, skip extra blank lines, use **“nothing was run”** instead of success/saved time, and omit the `--last-details` hint. **`-v`** detailed summaries drop exit icons and saved-time wording for hits. > > **Bugfix:** runs now **exit 1** when a task’s cache cannot be checked (e.g. DB unreadable), including failures before `start()`; nested `vp run --dry-run` is not inlined. Changelog and dry-run RFC/e2e snapshots document the behavior. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 3010605. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Aaron Reisman <aaron@lifeiscontent.net> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Motivation
Closes #791. There's no way to see which tasks a run would take from the cache without running them.
Changes
vp run --dry-run <task>plans the run as usual and prints, per task, a cache hit, a miss with the summary's reason, cache disabled, or unknown when something it runs after isn't a hit.--last-detailsis left alone. The remote cache isn't queried.try_hit_localintotry_hit, so normal runs behave the same and the dry run can share the lookup.Displayimpl so the summary and the dry run use the same text.