Skip to content

Commit 4bbae48

Browse files
authored
feat(task): add subcommand "cache clean" & "cache view"
(#105) This PR changes the task cache to use a directory structure instead of a single database file: - Changes cache path from `node_modules/.vite/task-cache.db` to `node_modules/.vite/task-cache` for easier clean - The SQLite database is now stored as `cache.db` inside this directory - Updates documentation to reflect the new cache location Adds new cache management commands: - `vite-plus cache clean` - Removes the cache directory - `vite-plus cache view` - Displays cache contents in JSON format for debugging Adds test cases to verify cache cleaning works correctly, including from subfolders.
1 parent f5629c7 commit 4bbae48

10 files changed

Lines changed: 129 additions & 34 deletions

File tree

bench/benches/workspace_load.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ fn bench_workspace_load(c: &mut Criterion) {
3434

3535
// Benchmark load with cache verification
3636
group.bench_function("load_with_cache_path", |b| {
37-
let cache_path = fixture_path.join("node_modules/.vite/task-cache.db");
37+
let cache_path = fixture_path.join("node_modules/.vite/task-cache");
3838
b.iter(|| {
3939
let workspace = black_box(
4040
Workspace::load_with_cache_path(

crates/vite_task/docs/task-cache.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -492,15 +492,15 @@ stmt.execute(params![key_bytes, value_bytes])?;
492492

493493
## Configuration
494494

495-
### Cache File Location
495+
### Cache Location
496496

497-
The cache database location can be configured via environment variable:
497+
The cache location can be configured via environment variable:
498498

499499
```bash
500500
# Custom cache location
501-
VITE_CACHE_PATH=/tmp/vite-cache.db vite-plus run build
501+
VITE_CACHE_PATH=/tmp/vite-cache vite-plus run build
502502

503-
# Default: .vite/cache.db in workspace root
503+
# Default: node_modules/.vite/task-cache in workspace root
504504
vite-plus run build
505505
```
506506

crates/vite_task/src/cache.rs

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use diff::Diff;
22
use rusqlite::config::DbConfig;
33
use std::fmt::Display;
4+
use std::io::Write;
45
use std::sync::Arc;
56
use vite_path::AbsolutePath;
67

@@ -79,9 +80,13 @@ impl Display for FingerprintMismatch {
7980
}
8081

8182
impl TaskCache {
82-
pub fn load_from_file(path: impl AsRef<AbsolutePath>) -> Result<Self, Error> {
83+
pub fn load_from_path(path: impl AsRef<AbsolutePath>) -> Result<Self, Error> {
8384
let path = path.as_ref();
84-
let conn = Connection::open(path.as_path())?;
85+
tracing::info!("Creating task cache directory at {:?}", path);
86+
std::fs::create_dir_all(path)?;
87+
88+
let db_path = path.join("cache.db");
89+
let conn = Connection::open(db_path.as_path())?;
8590
conn.execute_batch("PRAGMA journal_mode=WAL; BEGIN EXCLUSIVE;")?;
8691
loop {
8792
let user_version: u32 = conn.query_one("PRAGMA user_version", (), |row| row.get(0))?;
@@ -96,16 +101,10 @@ impl TaskCache {
96101
"CREATE TABLE taskrun_to_command (key BLOB PRIMARY KEY, value BLOB);",
97102
(),
98103
)?;
99-
conn.execute("PRAGMA user_version = 2", ())?;
100-
}
101-
1 => {
102-
// internal versions during dev, we just rebuild the whole cache
103-
conn.set_db_config(DbConfig::SQLITE_DBCONFIG_RESET_DATABASE, true)?;
104-
conn.execute("VACUUM", ())?;
105-
conn.set_db_config(DbConfig::SQLITE_DBCONFIG_RESET_DATABASE, false)?;
104+
conn.execute("PRAGMA user_version = 1", ())?;
106105
}
107-
2 => break, // current version
108-
3.. => return Err(Error::UnrecognizedDbVersion(user_version)),
106+
1 => break, // current version
107+
2.. => return Err(Error::UnrecognizedDbVersion(user_version)),
109108
}
110109
}
111110
conn.execute_batch("COMMIT")?;
@@ -240,4 +239,34 @@ impl TaskCache {
240239
) -> Result<(), Error> {
241240
self.upsert("taskrun_to_command", task_run_key, command_fingerprint).await
242241
}
242+
243+
async fn list_table<K: Decode<()> + Serialize, V: Decode<()> + Serialize>(
244+
&self,
245+
table: &str,
246+
out: &mut impl Write,
247+
) -> Result<(), Error> {
248+
let conn = self.conn.lock().await;
249+
let mut select_stmt = conn.prepare_cached(&format!("SELECT key, value FROM {}", table))?;
250+
let mut rows = select_stmt.query([])?;
251+
while let Some(row) = rows.next()? {
252+
let key_blob: Vec<u8> = row.get(0)?;
253+
let value_blob: Vec<u8> = row.get(1)?;
254+
let (key, _) = decode_from_slice::<K, _>(&key_blob, BINCODE_CONFIG)?;
255+
let (value, _) = decode_from_slice::<V, _>(&value_blob, BINCODE_CONFIG)?;
256+
writeln!(
257+
out,
258+
"{} => {}",
259+
serde_json::to_string_pretty(&key)?,
260+
serde_json::to_string_pretty(&value)?
261+
)?;
262+
}
263+
Ok(())
264+
}
265+
pub async fn list(&self, mut out: impl Write) -> Result<(), Error> {
266+
out.write_all(b"------- taskrun_to_command -------\n")?;
267+
self.list_table::<TaskRunKey, CommandFingerprint>("taskrun_to_command", &mut out).await?;
268+
out.write_all(b"------- command_cache -------\n")?;
269+
self.list_table::<CommandFingerprint, CommandCacheValue>("command_cache", &mut out).await?;
270+
Ok(())
271+
}
243272
}

crates/vite_task/src/config/workspace.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,19 @@ impl Workspace {
6969
Self::partial_load_with_cache_path(cwd, None)
7070
}
7171

72+
fn get_cache_path_of_workspace(workspace_root: &AbsolutePath) -> AbsolutePathBuf {
73+
if let Ok(env_cache_path) = std::env::var("VITE_CACHE_PATH") {
74+
AbsolutePathBuf::new(env_cache_path.into()).expect("Cache path should be absolute")
75+
} else {
76+
workspace_root.join("node_modules/.vite/task-cache")
77+
}
78+
}
79+
80+
pub fn get_cache_path(cwd: &AbsolutePath) -> Result<AbsolutePathBuf, Error> {
81+
let (workspace_root, _, _) = Self::determine_current_package_path(cwd)?;
82+
Ok(Self::get_cache_path_of_workspace(workspace_root))
83+
}
84+
7285
pub fn partial_load_with_cache_path(
7386
cwd: AbsolutePathBuf,
7487
cache_path: Option<AbsolutePathBuf>,
@@ -77,21 +90,16 @@ impl Workspace {
7790
let (workspace_root, cwd, current_package_path) =
7891
Self::determine_current_package_path(&cwd)?;
7992

80-
let cache_path = cache_path.unwrap_or_else(|| {
81-
if let Ok(env_cache_path) = std::env::var("VITE_CACHE_PATH") {
82-
AbsolutePathBuf::new(env_cache_path.into()).expect("Cache path should be absolute")
83-
} else {
84-
workspace_root.join("node_modules/.vite/task-cache.db")
85-
}
86-
});
93+
let cache_path =
94+
cache_path.unwrap_or_else(|| Self::get_cache_path_of_workspace(&workspace_root));
8795

8896
if !cache_path.as_path().exists()
8997
&& let Some(cache_dir) = cache_path.as_path().parent()
9098
{
9199
tracing::info!("Creating task cache directory at {}", cache_dir.display());
92100
std::fs::create_dir_all(cache_dir)?;
93101
}
94-
let task_cache = TaskCache::load_from_file(&cache_path)?;
102+
let task_cache = TaskCache::load_from_path(&cache_path)?;
95103

96104
let package_json_path = workspace_root.join("package.json");
97105
let package_json = if package_json_path.as_path().exists() {
@@ -137,21 +145,16 @@ impl Workspace {
137145
}
138146
}
139147

140-
let cache_path = cache_path.unwrap_or_else(|| {
141-
if let Ok(env_cache_path) = std::env::var("VITE_CACHE_PATH") {
142-
AbsolutePathBuf::new(env_cache_path.into()).expect("Cache path should be absolute")
143-
} else {
144-
workspace_root.join("node_modules/.vite/task-cache.db")
145-
}
146-
});
148+
let cache_path =
149+
cache_path.unwrap_or_else(|| Self::get_cache_path_of_workspace(workspace_root));
147150

148151
if !cache_path.as_path().exists()
149152
&& let Some(cache_dir) = cache_path.as_path().parent()
150153
{
151154
tracing::info!("Creating task cache directory at {}", cache_dir.display());
152155
std::fs::create_dir_all(cache_dir)?;
153156
}
154-
let task_cache = TaskCache::load_from_file(&cache_path)?;
157+
let task_cache = TaskCache::load_from_path(&cache_path)?;
155158

156159
// Build the complete task graph
157160
let mut task_graph_builder = TaskGraphBuilder::default();

crates/vite_task/src/lib.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use clap::{Parser, Subcommand};
2626

2727
use vite_str::Str;
2828

29-
use crate::schedule::ExecutionPlan;
29+
use crate::{cache::TaskCache, schedule::ExecutionPlan};
3030

3131
pub(crate) use vite_error::Error;
3232

@@ -100,6 +100,19 @@ pub enum Commands {
100100
/// Arguments to pass to vite install
101101
args: Vec<String>,
102102
},
103+
/// Manage the task cache
104+
Cache {
105+
#[clap(subcommand)]
106+
subcmd: CacheSubcommand,
107+
},
108+
}
109+
110+
#[derive(Subcommand, Debug)]
111+
pub enum CacheSubcommand {
112+
/// Clean up all the cache
113+
Clean,
114+
/// View the cache entries in json for debugging purpose
115+
View,
103116
}
104117

105118
/// Resolve boolean flag value considering both positive and negative forms.
@@ -270,6 +283,19 @@ pub async fn main<
270283
install::InstallCommand::builder(cwd).build().execute(&args).await?;
271284
return Ok(());
272285
}
286+
Some(Commands::Cache { subcmd }) => {
287+
let cache_path = Workspace::get_cache_path(&cwd)?;
288+
match subcmd {
289+
CacheSubcommand::Clean => {
290+
std::fs::remove_dir_all(&cache_path)?;
291+
}
292+
CacheSubcommand::View => {
293+
let cache = TaskCache::load_from_path(&cache_path)?;
294+
cache.list(std::io::stdout()).await?;
295+
}
296+
}
297+
return Ok(());
298+
}
273299
None => {
274300
let workspace = Workspace::load(cwd, false)?;
275301
// in implicit mode, vite-plus will run the task in the current package, replace the `pnpm/yarn/npm run` command.

crates/vite_task/src/test_utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ where
77
{
88
let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
99
let cache_path =
10-
AbsolutePath::new(temp_dir.path()).unwrap().join(&format!("vite-test-{}.db", test_name));
10+
AbsolutePath::new(temp_dir.path()).unwrap().join(&format!("vite-test-{}", test_name));
1111

1212
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(cache_path)));
1313

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"scripts": {
3+
"hello": "echo hello"
4+
}
5+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
> vite-plus run hello # create the cache for 'echo hello'
2+
Cache not found
3+
hello
4+
5+
> vite-plus run hello # hit the cache
6+
Cache hit, replaying
7+
hello
8+
9+
> vite-plus cache clean # clean the cache
10+
11+
> vite-plus run hello # cache miss after clean
12+
Cache not found
13+
hello
14+
15+
> cd subfolder && vite-plus cache clean # cache can be located and cleaned from subfolder
16+
17+
> vite-plus run hello # cache miss after clean
18+
Cache not found
19+
hello
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"env": {
3+
"VITE_DISABLE_AUTO_INSTALL": "1"
4+
},
5+
"commands": [
6+
"vite-plus run hello # create the cache for 'echo hello'",
7+
"vite-plus run hello # hit the cache",
8+
"vite-plus cache clean # clean the cache",
9+
"vite-plus run hello # cache miss after clean",
10+
"cd subfolder && vite-plus cache clean # cache can be located and cleaned from subfolder",
11+
"vite-plus run hello # cache miss after clean"
12+
]
13+
}

packages/cli/tests/cases/cache-clean/subfolder/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)