diff --git a/crates/vt_bin/src/vtt/main.rs b/crates/vt_bin/src/vtt/main.rs index bd5cff2e4..eb3f8bb62 100644 --- a/crates/vt_bin/src/vtt/main.rs +++ b/crates/vt_bin/src/vtt/main.rs @@ -20,6 +20,7 @@ mod print_color; mod print_cwd; mod print_env; mod print_file; +mod read_link; mod read_stdin; mod replace_file_content; mod rm; @@ -38,7 +39,7 @@ fn main() { if args.len() < 2 { eprintln!("Usage: vtt [args...]"); eprintln!( - "Subcommands: barrier, check-tty, cp, exit, exit-on-ctrlc, grep-file, list-dir, mkdir, pipe-stdin, print, print-color, print-cwd, print-env, print-file, read-stdin, replace-file-content, rm, small_dev_shm, stalled-remote-cache, stat-file, stat-many, symlink, touch-file, write-file" + "Subcommands: barrier, check-tty, cp, exit, exit-on-ctrlc, grep-file, list-dir, mkdir, pipe-stdin, print, print-color, print-cwd, print-env, print-file, read-link, read-stdin, replace-file-content, rm, small_dev_shm, stalled-remote-cache, stat-file, stat-many, symlink, touch-file, write-file" ); std::process::exit(1); } @@ -67,6 +68,7 @@ fn main() { "print-cwd" => print_cwd::run(), "print-env" => print_env::run(&args[2..]), "print-file" => print_file::run(&args[2..]), + "read-link" => read_link::run(&args[2..]), "read-stdin" => read_stdin::run(), "replace-file-content" => replace_file_content::run(&args[2..]), "rm" => rm::run(&args[2..]), diff --git a/crates/vt_bin/src/vtt/read_link.rs b/crates/vt_bin/src/vtt/read_link.rs new file mode 100644 index 000000000..28a8d5965 --- /dev/null +++ b/crates/vt_bin/src/vtt/read_link.rs @@ -0,0 +1,9 @@ +/// Print the target of the symbolic link at ``, like `readlink`. Fails if +/// `` isn't a symbolic link. +pub fn run(args: &[String]) -> Result<(), Box> { + let [path] = args else { + return Err("Usage: vtt read-link ".into()); + }; + println!("{}", std::fs::read_link(path)?.display()); + Ok(()) +} diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/nitro_build_cache/check.cjs b/crates/vt_bin/tests/e2e_snapshots/fixtures/nitro_build_cache/check.cjs index a451f9d75..6e5fa3186 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/nitro_build_cache/check.cjs +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/nitro_build_cache/check.cjs @@ -1,6 +1,18 @@ -// Prints the `tslib` version each traced package resolves to in the server output. -const modules = './.output/server/node_modules/'; +// Checks that each path nf3 links is a symbolic link into `.nf3`, then prints +// the `tslib` version each traced package resolves to through the links. +const fs = require('node:fs'); +const path = require('node:path'); + +const modules = path.resolve('.output/server/node_modules'); +for (const link of ['tslib', 'dep-a/node_modules/tslib', 'dep-b/node_modules/tslib']) { + const linkPath = path.join(modules, link); + const kind = fs.lstatSync(linkPath).isSymbolicLink() ? 'link' : 'not a link'; + // Resolve both ends, so the output is the same whether the target is + // relative (Unix) or an absolute junction target (Windows). + const target = path.relative(fs.realpathSync(path.dirname(linkPath)), fs.realpathSync(linkPath)); + console.log(`${link}: ${kind} to ${target.split(path.sep).join('/')}`); +} for (const name of ['dep-a', 'dep-b']) { - console.log(`${name}: tslib ${require(modules + name)()}`); + console.log(`${name}: tslib ${require(path.join(modules, name))()}`); } -console.log(`hoisted: tslib ${require(modules + 'tslib').version}`); +console.log(`hoisted: tslib ${require(path.join(modules, 'tslib')).version}`); diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/nitro_build_cache/snapshots.toml b/crates/vt_bin/tests/e2e_snapshots/fixtures/nitro_build_cache/snapshots.toml index a5b9528c2..422b82f62 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/nitro_build_cache/snapshots.toml +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/nitro_build_cache/snapshots.toml @@ -1,7 +1,7 @@ [[e2e]] name = "nitro_build_restores_traced_dependency_links" comment = """ -A real Nitro server build traces `dep-a` and `dep-b`, which depend on different versions of `tslib` (small stand-ins committed in the fixture's `node_modules`). nf3 writes each version under `.output/server/node_modules/.nf3/tslib@`, links `node_modules/tslib` to the hoisted one, and links each package's `node_modules/tslib` to the one it needs. A cache hit restores those links, so each package still resolves its own version from the restored output. On Windows, nf3 creates junctions, whose targets are absolute paths. +A real Nitro server build traces `dep-a` and `dep-b`, which depend on different versions of `tslib` (small stand-ins committed in the fixture's `node_modules`). nf3 writes each version under `.output/server/node_modules/.nf3/tslib@`, links `node_modules/tslib` to the hoisted one, and links each package's `node_modules/tslib` to the one it needs. A cache hit restores those links as links, so each package still resolves its own version from the restored output. On Windows, nf3 creates junctions, whose targets are absolute paths. """ ignore = true steps = [ @@ -13,7 +13,7 @@ steps = [ { argv = [ "node", "check.cjs", - ], comment = "each package resolves its own tslib through nf3's links" }, + ], comment = "nf3 linked each package's tslib into .nf3, so each resolves its own version" }, { argv = [ "vtt", "rm", @@ -28,5 +28,5 @@ steps = [ { argv = [ "node", "check.cjs", - ], comment = "the restored output resolves the same versions" }, + ], comment = "the restored output has the same links, not copies, and resolves the same versions" }, ] diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/nitro_build_cache/snapshots/nitro_build_restores_traced_dependency_links.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/nitro_build_cache/snapshots/nitro_build_restores_traced_dependency_links.md index 58a96b2c0..da4c31d01 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/nitro_build_cache/snapshots/nitro_build_restores_traced_dependency_links.md +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/nitro_build_cache/snapshots/nitro_build_restores_traced_dependency_links.md @@ -1,6 +1,6 @@ # nitro_build_restores_traced_dependency_links -A real Nitro server build traces `dep-a` and `dep-b`, which depend on different versions of `tslib` (small stand-ins committed in the fixture's `node_modules`). nf3 writes each version under `.output/server/node_modules/.nf3/tslib@`, links `node_modules/tslib` to the hoisted one, and links each package's `node_modules/tslib` to the one it needs. A cache hit restores those links, so each package still resolves its own version from the restored output. On Windows, nf3 creates junctions, whose targets are absolute paths. +A real Nitro server build traces `dep-a` and `dep-b`, which depend on different versions of `tslib` (small stand-ins committed in the fixture's `node_modules`). nf3 writes each version under `.output/server/node_modules/.nf3/tslib@`, links `node_modules/tslib` to the hoisted one, and links each package's `node_modules/tslib` to the one it needs. A cache hit restores those links as links, so each package still resolves its own version from the restored output. On Windows, nf3 creates junctions, whose targets are absolute paths. ## `vt run build` @@ -12,9 +12,12 @@ $ CONSOLA_LEVEL=0 nitro build ## `node check.cjs` -each package resolves its own tslib through nf3's links +nf3 linked each package's tslib into .nf3, so each resolves its own version ``` +tslib: link to .nf3/tslib@2.8.1 +dep-a/node_modules/tslib: link to ../../.nf3/tslib@1.14.1 +dep-b/node_modules/tslib: link to ../../.nf3/tslib@2.8.1 dep-a: tslib 1.14.1 dep-b: tslib 2.8.1 hoisted: tslib 2.8.1 @@ -40,9 +43,12 @@ vt run: cache hit. ## `node check.cjs` -the restored output resolves the same versions +the restored output has the same links, not copies, and resolves the same versions ``` +tslib: link to .nf3/tslib@2.8.1 +dep-a/node_modules/tslib: link to ../../.nf3/tslib@1.14.1 +dep-b/node_modules/tslib: link to ../../.nf3/tslib@2.8.1 dep-a: tslib 1.14.1 dep-b: tslib 2.8.1 hoisted: tslib 2.8.1 diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml b/crates/vt_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml index fd54f3af1..0bc2e8846 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml @@ -159,7 +159,7 @@ steps = [ [[e2e]] name = "output_globs___symlinks_restored_on_cache_hit" comment = """ -A symbolic link matched by an output glob is archived as a link, so a cache hit restores it. Restoring also replaces a link that is already there. Unix only: on Windows, `tar` restores the target `store/pkg` with its `/` separator, which Windows doesn't resolve, and can't replace the directory link already there. +A symbolic link matched by an output glob is archived as a link, so a cache hit restores it as a link with the same target. Restoring also replaces a link that is already there. Unix only: on Windows, `tar` restores the target `store/pkg` with its `/` separator, which Windows doesn't resolve, and can't replace the directory link already there. """ cfg = "unix" steps = [ @@ -184,6 +184,11 @@ steps = [ "print-file", "dist/pkg/index.js", ], comment = "the file is reachable through the restored link" }, + { argv = [ + "vtt", + "read-link", + "dist/pkg", + ], comment = "dist/pkg is restored as a link to store/pkg, not as a copy" }, { argv = [ "vt", "run", @@ -194,4 +199,9 @@ steps = [ "print-file", "dist/pkg/index.js", ], comment = "the file is still reachable through the link" }, + { argv = [ + "vtt", + "read-link", + "dist/pkg", + ], comment = "dist/pkg is still a link to store/pkg" }, ] diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___symlinks_restored_on_cache_hit.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___symlinks_restored_on_cache_hit.md index a73f2c8b7..c4e818eb5 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___symlinks_restored_on_cache_hit.md +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___symlinks_restored_on_cache_hit.md @@ -1,6 +1,6 @@ # output_globs___symlinks_restored_on_cache_hit -A symbolic link matched by an output glob is archived as a link, so a cache hit restores it. Restoring also replaces a link that is already there. Unix only: on Windows, `tar` restores the target `store/pkg` with its `/` separator, which Windows doesn't resolve, and can't replace the directory link already there. +A symbolic link matched by an output glob is archived as a link, so a cache hit restores it as a link with the same target. Restoring also replaces a link that is already there. Unix only: on Windows, `tar` restores the target `store/pkg` with its `/` separator, which Windows doesn't resolve, and can't replace the directory link already there. ## `vt run build-with-symlink` @@ -43,6 +43,14 @@ the file is reachable through the restored link built ``` +## `vtt read-link dist/pkg` + +dist/pkg is restored as a link to store/pkg, not as a copy + +``` +store/pkg +``` + ## `vt run build-with-symlink` third run — cache hit, restores over the link the second run left @@ -63,3 +71,11 @@ the file is still reachable through the link ``` built ``` + +## `vtt read-link dist/pkg` + +dist/pkg is still a link to store/pkg + +``` +store/pkg +```