Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions crates/vite_task_bin/src/vtt/cp.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
pub fn run(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
if args.len() != 2 {
return Err("Usage: vtt cp <src> <dst>".into());
let (recursive, paths) = match args {
[flag, src, dst] if flag == "-r" => (true, [src.as_str(), dst.as_str()]),
[src, dst] => (false, [src.as_str(), dst.as_str()]),
_ => return Err("Usage: vtt cp [-r] <src> <dst>".into()),
};

let src = std::path::Path::new(paths[0]);
let dst = std::path::Path::new(paths[1]);
if src.is_dir() {
if !recursive {
return Err("copying a directory requires -r".into());
}
copy_dir_recursive(src, dst)?;
} else {
std::fs::copy(src, dst)?;
}
Ok(())
}

fn copy_dir_recursive(
src: &std::path::Path,
dst: &std::path::Path,
) -> Result<(), Box<dyn std::error::Error>> {
std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
let file_type = entry.file_type()?;
if file_type.is_dir() {
copy_dir_recursive(&src_path, &dst_path)?;
} else if file_type.is_file() {
std::fs::copy(&src_path, &dst_path)?;
}
}
std::fs::copy(&args[0], &args[1])?;
Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "vite-build-cache-portable-fixture",
"private": true,
"type": "module"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('plain vite build');
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"tasks": {
"build": {
// TODO: --configLoader runner works around a Vite config-loader bug that makes this cache non-portable.
"command": "vite build --configLoader runner",
"cache": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from 'vite';

export default defineConfig({
logLevel: 'silent',
build: {
rollupOptions: {
output: {
entryFileNames: 'assets/index.js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name][extname]',
},
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "vite-build-cache-portable-fixture",
"private": true,
"type": "module"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('plain vite build');
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"tasks": {
"build": {
// TODO: --configLoader runner works around a Vite config-loader bug that makes this cache non-portable.
"command": "vite build --configLoader runner",
"cache": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from 'vite';

export default defineConfig({
logLevel: 'silent',
build: {
rollupOptions: {
output: {
entryFileNames: 'assets/index.js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name][extname]',
},
},
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,44 @@ steps = [
], comment = "cache-bravo label is now in the bundle" },
]

[[e2e]]
name = "vite_build_cache_is_portable_across_workspace_roots"
comment = """
Two identical plain Vite workspaces run from different absolute roots. The test copies the origin root's default task cache directory into the clone root to simulate uploading and downloading cache data; the clone should hit that copied cache and restore build outputs.
"""
ignore = true
steps = [
{ cwd = "portable_origin", argv = [
"vt",
"run",
"--cache",
"build",
], comment = "origin workspace root: cache miss populates its default cache" },
{ cwd = "portable_origin", argv = [
"vtt",
"stat-file",
"dist/assets/index.js",
], comment = "origin emitted the non-hashed build asset" },
{ argv = [
"vtt",
"cp",
"-r",
"portable_origin/node_modules/.vite/task-cache",
"portable_clone/node_modules/.vite/task-cache",
], comment = "copy-paste the cache directory to simulate upload/download" },
{ cwd = "portable_clone", argv = [
"vt",
"run",
"--cache",
"build",
], comment = "clone workspace root: cache hit from the origin root" },
{ cwd = "portable_clone", argv = [
"vtt",
"stat-file",
"dist/assets/index.js",
], comment = "clone restored the non-hashed asset from the portable cache archive" },
]

[[e2e]]
name = "vite_node_env_change_invalidates_cache"
comment = """
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# vite_build_cache_is_portable_across_workspace_roots

Two identical plain Vite workspaces run from different absolute roots. The test copies the origin root's default task cache directory into the clone root to simulate uploading and downloading cache data; the clone should hit that copied cache and restore build outputs.

## `cd portable_origin && vt run --cache build`

origin workspace root: cache miss populates its default cache

```
$ vite build --configLoader runner
```

## `cd portable_origin && vtt stat-file dist/assets/index.js`

origin emitted the non-hashed build asset

```
dist/assets/index.js: exists
```

## `vtt cp -r portable_origin/node_modules/.vite/task-cache portable_clone/node_modules/.vite/task-cache`

copy-paste the cache directory to simulate upload/download

```
```

## `cd portable_clone && vt run --cache build`

clone workspace root: cache hit from the origin root

```
$ vite build --configLoader runner ◉ cache hit, replaying

---
vt run: cache hit.
```

## `cd portable_clone && vtt stat-file dist/assets/index.js`

clone restored the non-hashed asset from the portable cache archive

```
dist/assets/index.js: exists
```
46 changes: 34 additions & 12 deletions crates/vite_task_bin/tests/e2e_snapshots/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ enum Step {
#[serde(deny_unknown_fields)]
struct StepConfig {
argv: Vec1<Str>,
/// Optional working directory for this step, relative to the staged fixture root.
/// Defaults to the case-level `cwd`.
#[serde(default)]
cwd: Option<RelativePathBuf>,
/// Appended as `# comment` in the snapshot display line.
#[serde(default)]
comment: Option<Str>,
Expand All @@ -61,11 +65,11 @@ impl Step {
}
}

/// Shell-escaped command line including any env-var prefix, without the
/// comment (e.g. `MY_ENV=1 vt run test`). The comment is surfaced
/// separately by [`Self::comment`].
/// Shell-escaped command line including any env-var prefix and non-default
/// cwd, without the comment (e.g. `cd packages/a && MY_ENV=1 vt run test`).
/// The comment is surfaced separately by [`Self::comment`].
#[expect(clippy::disallowed_types, reason = "String required by join/format")]
fn display_command_line(&self) -> String {
fn display_command_line(&self, default_cwd: &RelativePathBuf) -> String {
let argv_str = self
.argv()
.iter()
Expand All @@ -80,7 +84,7 @@ impl Step {
.collect::<Vec<_>>()
.join(" ");

match self {
let command = match self {
Self::Simple(_) => argv_str,
Self::Detailed(config) => {
let mut parts = String::new();
Expand All @@ -90,6 +94,19 @@ impl Step {
parts.push_str(&argv_str);
parts
}
};

let cwd = self.cwd().unwrap_or(default_cwd);
if cwd == default_cwd {
command
} else {
let cwd = if cwd.as_str().is_empty() { "." } else { cwd.as_str() };
let mut display = String::new();
display.push_str("cd ");
display.push_str(shell_escape::escape(cwd.into()).as_ref());
display.push_str(" && ");
display.push_str(&command);
display
}
}

Expand All @@ -114,6 +131,13 @@ impl Step {
}
}

const fn cwd(&self) -> Option<&RelativePathBuf> {
match self {
Self::Detailed(config) => config.cwd.as_ref(),
Self::Simple(_) => None,
}
}

const fn formatted_snapshot(&self) -> bool {
match self {
Self::Detailed(config) => config.formatted_snapshot,
Expand Down Expand Up @@ -247,11 +271,8 @@ enum TerminationState {
}

/// Substitutes sentinels in step env values with values only known at
/// test-run time. Currently supports `<PRELOAD_TEST_LIB_PATH>`, which
/// expands to the path of the `preload_test_lib` cdylib built via the
/// artifact dependency (Linux only — the sentinel is only used by the
/// `preload_test_lib`-gated e2e fixture). Keeps the raw sentinel in the
/// snapshot's displayed command line, so snapshots stay machine-independent.
/// test-run time. Keeps the raw sentinel in the snapshot's displayed command
/// line, so snapshots stay machine-independent.
fn resolve_env_placeholder(raw: &str) -> std::borrow::Cow<'_, OsStr> {
if raw == "<PRELOAD_TEST_LIB_PATH>" {
let path = env::var_os("CARGO_CDYLIB_FILE_PRELOAD_TEST_LIB").unwrap_or_else(|| {
Expand Down Expand Up @@ -395,7 +416,7 @@ fn run_case(
}
{
for step in &e2e.steps {
let step_display = step.display_command_line();
let step_display = step.display_command_line(&e2e.cwd);
let step_comment = step.comment().map(str::to_owned);

let argv = step.argv();
Expand Down Expand Up @@ -455,7 +476,8 @@ fn run_case(
let resolved = resolve_env_placeholder(v.as_str());
cmd.env(k.as_str(), AsRef::<OsStr>::as_ref(&resolved));
}
cmd.cwd(e2e_stage_path.join(&e2e.cwd).as_path());
let step_cwd = step.cwd().unwrap_or(&e2e.cwd);
cmd.cwd(e2e_stage_path.join(step_cwd).as_path());

let terminal = TestTerminal::spawn(SCREEN_SIZE, cmd).unwrap();
let mut killer = terminal.child_handle.clone();
Expand Down
Loading