diff --git a/packages/cli/binding/src/cli.rs b/packages/cli/binding/src/cli.rs index 1ede4a2146..7dd66bf906 100644 --- a/packages/cli/binding/src/cli.rs +++ b/packages/cli/binding/src/cli.rs @@ -517,6 +517,21 @@ fn build_pack_cache_inputs() -> Vec { ] } +/// Cache input entries for the check command. +/// The vp check subprocess is a full vp CLI process (not resolved to a binary like +/// build/lint/fmt), so it accesses additional directories that must be excluded: +/// - `.vite-temp`: config compilation cache, read+written during vp CLI startup +/// - `.vite/task-cache`: task runner state files that change after each run +fn check_cache_inputs() -> Vec { + vec![ + UserInputEntry::Auto(AutoInput { auto: true }), + exclude_glob("!node_modules/.vite-temp/**", InputBase::Workspace), + exclude_glob("!node_modules/.vite-temp/**", InputBase::Package), + exclude_glob("!node_modules/.vite/task-cache/**", InputBase::Workspace), + exclude_glob("!node_modules/.vite/task-cache/**", InputBase::Package), + ] +} + fn merge_resolved_envs( envs: &Arc, Arc>>, resolved_envs: Vec<(String, String)>, @@ -587,10 +602,14 @@ impl CommandHandler for VitePlusCommandHandler { }; match cli_args { CLIArgs::Synthesizable(SynthesizableSubcommand::Check { .. }) => { - // Check is a composite command — run as a subprocess in task scripts - Ok(HandledCommand::Synthesized( - command.to_synthetic_plan_request(UserCacheConfig::disabled()), - )) + // Check is a composite command (fmt + lint) — run as a subprocess in task scripts + Ok(HandledCommand::Synthesized(command.to_synthetic_plan_request( + UserCacheConfig::with_config(EnabledCacheConfig { + env: Some(Box::new([Str::from("OXLINT_TSGOLINT_PATH")])), + untracked_env: None, + input: Some(check_cache_inputs()), + }), + ))) } CLIArgs::Synthesizable(subcmd) => { let resolved = diff --git a/packages/cli/snap-tests/check-cache-disabled/package.json b/packages/cli/snap-tests/check-cache-disabled/package.json new file mode 100644 index 0000000000..ece867044b --- /dev/null +++ b/packages/cli/snap-tests/check-cache-disabled/package.json @@ -0,0 +1,7 @@ +{ + "name": "check-cache-disabled-test", + "type": "module", + "scripts": { + "check": "vp check" + } +} diff --git a/packages/cli/snap-tests/check-cache-disabled/snap.txt b/packages/cli/snap-tests/check-cache-disabled/snap.txt new file mode 100644 index 0000000000..0f813d28d4 --- /dev/null +++ b/packages/cli/snap-tests/check-cache-disabled/snap.txt @@ -0,0 +1,5 @@ +> vp run check # vp check should have cache disabled without run.cache +$ vp check ⊘ cache disabled +pass: All 3 files are correctly formatted (ms, threads) +pass: Found no warnings or lint errors in 1 file (ms, threads) + diff --git a/packages/cli/snap-tests/check-cache-disabled/src/index.js b/packages/cli/snap-tests/check-cache-disabled/src/index.js new file mode 100644 index 0000000000..13305bd3e9 --- /dev/null +++ b/packages/cli/snap-tests/check-cache-disabled/src/index.js @@ -0,0 +1,5 @@ +function hello() { + return "hello"; +} + +export { hello }; diff --git a/packages/cli/snap-tests/check-cache-disabled/steps.json b/packages/cli/snap-tests/check-cache-disabled/steps.json new file mode 100644 index 0000000000..f58ee79361 --- /dev/null +++ b/packages/cli/snap-tests/check-cache-disabled/steps.json @@ -0,0 +1,3 @@ +{ + "commands": ["vp run check # vp check should have cache disabled without run.cache"] +} diff --git a/packages/cli/snap-tests/check-cache-enabled/package.json b/packages/cli/snap-tests/check-cache-enabled/package.json new file mode 100644 index 0000000000..997527d326 --- /dev/null +++ b/packages/cli/snap-tests/check-cache-enabled/package.json @@ -0,0 +1,7 @@ +{ + "name": "check-cache-enabled-test", + "type": "module", + "scripts": { + "check": "vp check" + } +} diff --git a/packages/cli/snap-tests/check-cache-enabled/snap.txt b/packages/cli/snap-tests/check-cache-enabled/snap.txt new file mode 100644 index 0000000000..53f5b97163 --- /dev/null +++ b/packages/cli/snap-tests/check-cache-enabled/snap.txt @@ -0,0 +1,20 @@ +> vp run check # first run should be cache miss +$ vp check +pass: All 4 files are correctly formatted (ms, threads) +pass: Found no warnings or lint errors in 2 files (ms, threads) + + +> vp run check # second run should be cache hit +$ vp check ◉ cache hit, replaying +pass: All 4 files are correctly formatted (ms, threads) +pass: Found no warnings or lint errors in 2 files (ms, threads) + +--- +vp run: cache hit, ms saved. + +> echo 'export const foo = 1;' > src/foo.js +> vp run check # third run should be cache miss after new file added +$ vp check ○ cache miss: 'foo.js' added in 'src', executing +pass: All 5 files are correctly formatted (ms, threads) +pass: Found no warnings or lint errors in 3 files (ms, threads) + diff --git a/packages/cli/snap-tests/check-cache-enabled/src/index.js b/packages/cli/snap-tests/check-cache-enabled/src/index.js new file mode 100644 index 0000000000..13305bd3e9 --- /dev/null +++ b/packages/cli/snap-tests/check-cache-enabled/src/index.js @@ -0,0 +1,5 @@ +function hello() { + return "hello"; +} + +export { hello }; diff --git a/packages/cli/snap-tests/check-cache-enabled/steps.json b/packages/cli/snap-tests/check-cache-enabled/steps.json new file mode 100644 index 0000000000..abcc3eae2e --- /dev/null +++ b/packages/cli/snap-tests/check-cache-enabled/steps.json @@ -0,0 +1,9 @@ +{ + "ignoredPlatforms": ["win32"], + "commands": [ + "vp run check # first run should be cache miss", + "vp run check # second run should be cache hit", + "echo 'export const foo = 1;' > src/foo.js", + "vp run check # third run should be cache miss after new file added" + ] +} diff --git a/packages/cli/snap-tests/check-cache-enabled/vite.config.ts b/packages/cli/snap-tests/check-cache-enabled/vite.config.ts new file mode 100644 index 0000000000..5f95ceeb78 --- /dev/null +++ b/packages/cli/snap-tests/check-cache-enabled/vite.config.ts @@ -0,0 +1,5 @@ +export default { + run: { + cache: true, + }, +}; diff --git a/rfcs/check-command.md b/rfcs/check-command.md index c1e164450e..1c76cffb3e 100644 --- a/rfcs/check-command.md +++ b/rfcs/check-command.md @@ -224,6 +224,45 @@ With `vp check`, the monorepo template's "ready" script simplifies to: "ready": "vp check && vp run -r test && vp run -r build" ``` +## Caching + +When `vp check` is used as a package.json script (e.g., `"check": "vp check"`) and executed via `vp run check`, it supports task runner caching like other synthesized commands (`vp build`, `vp lint`, `vp fmt`). + +### Configuration + +Enable caching in `vite.config.ts`: + +```ts +export default { + run: { + cache: true, + }, +}; +``` + +With caching enabled, the second `vp run check` replays cached output when inputs haven't changed: + +``` +$ vp check ◉ cache hit, replaying +pass: All 4 files are correctly formatted (105ms, 16 threads) +pass: Found no warnings or lint errors in 2 files (452ms, 16 threads) +``` + +### Cache key + +The check command's cache fingerprint includes: + +- **Environment variable:** `OXLINT_TSGOLINT_PATH` (affects lint behavior) +- **Input files:** Auto-tracked via fspy, excluding: + - `node_modules/.vite-temp/**` — config compilation cache (read+written by the vp CLI subprocess) + - `node_modules/.vite/task-cache/**` — task runner state files that change after each run + +These exclusions are shared with other synthesized commands via `base_cache_inputs()` in `cli.rs`. + +### How it differs from `vp fmt` / `vp lint` + +When `vp fmt` or `vp lint` appear in task scripts, the command handler resolves them to their underlying binaries (e.g., `node path/to/oxfmt.mjs`). The `vp check` command is different — it runs as a full `vp check` subprocess because it's a composite command that orchestrates both fmt and lint internally. This means the `vp` CLI process itself is tracked by fspy, which is why the `.vite-temp` and `.vite/task-cache` exclusions are necessary. + ## Comparison with Other Tools | Tool | Scope | @@ -252,4 +291,11 @@ packages/cli/snap-tests/check-no-fmt/ package.json steps.json # { "steps": [{ "command": "vp check --no-fmt" }] } snap.txt # Only lint runs + +packages/cli/snap-tests/check-cache-enabled/ + package.json # { "scripts": { "check": "vp check" } } + vite.config.ts # { run: { cache: true } } + steps.json # Runs vp run check twice, expects cache hit on second run + src/index.js + snap.txt ```