Skip to content
Closed
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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ vite-plus/
└── crates/vp_trampoline/ # Windows shim trampoline
```

On-disk paths (bin, config, data, state, cache) are resolved centrally via `vp_shared::Dirs` (`crates/vp_shared/src/dirs.rs`) — legacy monolithic `~/.vite-plus` root or split XDG/platform layout; no call site constructs `~/.vite-plus/...` or reads `XDG_*` itself.

`packages/test` is no longer tracked. The public test API is `vite-plus/test*`, generated by `packages/cli/build.ts` as shims over upstream `vitest` and `@vitest/browser*` exports.

## Where to Start
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pnpm bootstrap-cli
vp --version
```

This builds all packages, compiles the Rust `vp` binary, and installs the CLI to `~/.vite-plus`.
This builds all packages, compiles the Rust `vp` binary, and installs the CLI to `~/.vite-plus` (the legacy monolithic layout; on-disk paths are resolved by `vp_shared::Dirs` in `crates/vp_shared/src/dirs.rs`).

To switch back to a release version, use `vp upgrade --force` (`current` points to `local-dev-*` but the binary version may still match the release, so `--force` is needed)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import fs from 'node:fs';
import path from 'node:path';

const expected = path.resolve('external/vp');
// Shims of a legacy install are relative links into its own current/bin/vp.
const expected = path.join('..', 'current', 'bin', 'vp');

for (const shim of ['vp', 'node', 'npm', 'npx', 'corepack', 'vpx', 'vpr']) {
const shimPath = path.join('home', 'bin', shim);
const shimPath = path.join('external', 'bin', shim);
const target = fs.readlinkSync(shimPath);
if (target !== expected) {
throw new Error(`${shim} points to ${target}, expected ${expected}`);
}
}

console.log('all shims point to external vp');
console.log('all shims point to the external install');
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ name = "command_env_setup_external_vp"
vp = "global"
skip-platforms = ["windows"]
steps = [
{ argv = ["vpt", "mkdir", "-p", "external", "home"], comment = "Prepare isolated external install and VP_HOME", snapshot = false },
{ argv = ["vpt", "cp", "$VP_HOME/bin/vp", "external/vp"], comment = "Simulate a Homebrew-style vp outside VP_HOME", snapshot = false },
{ argv = ["vpt", "chmod", "+x", "external/vp"], snapshot = false },
{ argv = ["vpt", "mkdir", "-p", "external/current/bin", "external/bin", "external/js_runtime/node/22.18.0/bin"], comment = "A second, complete legacy install outside the case home", snapshot = false },
{ argv = ["vpt", "cp", "$VP_HOME/current/bin/vp", "external/current/bin/vp"], comment = "The external install's vp binary", snapshot = false },
{ argv = ["vpt", "cp", "$VP_HOME/current/bin/vp", "external/bin/vp"], comment = "Marks the external layout as a legacy install for detection", snapshot = false },
{ argv = ["vpt", "chmod", "+x", "external/current/bin/vp"], snapshot = false },
{ argv = ["vpt", "chmod", "+x", "external/bin/vp"], snapshot = false },
{ argv = ["vpt", "write-file", ".node-version", "22.18.0\n"], comment = "Project Node.js version", snapshot = false },
{ argv = ["vpt", "write-file", "home/js_runtime/node/22.18.0/bin/node", "#!/bin/sh\necho vp-managed-node-22.18.0\n"], comment = "Preinstall managed Node runtime", snapshot = false },
{ argv = ["vpt", "chmod", "+x", "home/js_runtime/node/22.18.0/bin/node"], snapshot = false },
{ argv = ["./external/vp", "env", "setup"], envs = [["VP_HOME", "${workspace}/home"]], comment = "Setup shims from external vp", snapshot = false },
{ argv = ["vpt", "write-file", "external/js_runtime/node/22.18.0/bin/node", "#!/bin/sh\necho vp-managed-node-22.18.0\n"], comment = "Preinstall managed Node runtime", snapshot = false },
{ argv = ["vpt", "chmod", "+x", "external/js_runtime/node/22.18.0/bin/node"], snapshot = false },
{ argv = ["./external/current/bin/vp", "env", "setup"], comment = "env setup targets the invoking install via self-location (no VP_HOME)", snapshot = false },
# The legacy step set VP_BYPASS to reach a system node, which the hermetic
# case PATH does not have; the node shim resolving the pinned 22.18.0 from
# the seeded runtime serves the same purpose (any node can run the asserts).
{ argv = ["node", "assert-shims.mjs"], comment = "Shims should point to external vp, not VP_HOME/current/bin/vp" },
{ argv = ["node", "-v"], envs = [["VP_HOME", "${workspace}/home"], ["PATH", "${workspace}/home/bin:${PATH}"]], comment = "node shim uses the project version" },
{ argv = ["node", "assert-shims.mjs"], comment = "Shims point to the external install's vp, not the case home's" },
{ argv = ["node", "-v"], envs = [["PATH", "${workspace}/external/bin:${PATH}"]], comment = "node shim uses the project version" },
]
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
# command_env_setup_external_vp

## `vpt mkdir -p external home`
## `vpt mkdir -p external/current/bin external/bin external/js_runtime/node/22.18.0/bin`

Prepare isolated external install and VP_HOME
A second, complete legacy install outside the case home


## `vpt cp $VP_HOME/bin/vp external/vp`
## `vpt cp $VP_HOME/current/bin/vp external/current/bin/vp`

Simulate a Homebrew-style vp outside VP_HOME
The external install's vp binary


## `vpt chmod +x external/vp`
## `vpt cp $VP_HOME/current/bin/vp external/bin/vp`

Marks the external layout as a legacy install for detection


## `vpt chmod +x external/current/bin/vp`


## `vpt chmod +x external/bin/vp`


## `vpt write-file .node-version '22.18.0
Expand All @@ -19,30 +27,30 @@ Simulate a Homebrew-style vp outside VP_HOME
Project Node.js version


## `vpt write-file home/js_runtime/node/22.18.0/bin/node '#'\!'/bin/sh
## `vpt write-file external/js_runtime/node/22.18.0/bin/node '#'\!'/bin/sh
echo vp-managed-node-22.18.0
'`

Preinstall managed Node runtime


## `vpt chmod +x home/js_runtime/node/22.18.0/bin/node`
## `vpt chmod +x external/js_runtime/node/22.18.0/bin/node`


## `VP_HOME=${workspace}/home ./external/vp env setup`
## `./external/current/bin/vp env setup`

Setup shims from external vp
env setup targets the invoking install via self-location (no VP_HOME)


## `node assert-shims.mjs`

Shims should point to external vp, not VP_HOME/current/bin/vp
Shims point to the external install's vp, not the case home's

```
all shims point to external vp
all shims point to the external install
```

## `VP_HOME=${workspace}/home PATH=${workspace}/home/bin:${PATH} node -v`
## `PATH=${workspace}/external/bin:${PATH} node -v`

node shim uses the project version

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,14 @@ d="$(dirname "$(dirname "$(dirname "$0")")")"
__vp_shell=/bin/sh
[ -x "$__vp_shell" ] || __vp_shell=$(command -v sh)

if [ -n "${VP_HOME-}" ]; then
if [ -n "${VP_BIN_DIR-}" ]; then
__vp_bin="$VP_BIN_DIR"
elif [ -n "${VP_HOME-}" ]; then
__vp_bin="$VP_HOME/bin"
elif [ -n "${HOME-}" ]; then
elif [ -n "${HOME-}" ] && [ -d "$HOME/.vite-plus/bin" ]; then
__vp_bin="$HOME/.vite-plus/bin"
elif [ -n "${HOME-}" ]; then
__vp_bin="$HOME/.local/bin"
else
__vp_bin=""
fi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
# Fake bundled corepack: echoes its invocation with the test root normalized
# for stable snapshots, and simulates corepack clobbering the npm shim on
# `enable` so the test can assert that Vite+ restores it.
# The script lives at <install>/js_runtime/node/<version>/bin/corepack, so the
# install's shim dir is three levels up plus `bin`.
if [ "$1" = "enable" ]; then
rm -f "$VP_HOME/bin/npm"
rm -f "$(dirname "$0")/../../../bin/npm"
fi
out="corepack"
for arg in "$@"; do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ name = "shim_corepack_enable_install_directory"
vp = "global"
skip-platforms = ["windows"]
steps = [
{ argv = ["vpt", "mkdir", "-p", "home/js_runtime/node/22.18.0/bin"], comment = "Isolated VP_HOME with a fake managed Node runtime layout", snapshot = false },
{ argv = ["vpt", "mkdir", "-p", "home/.vite-plus/js_runtime/node/22.18.0/bin", "home/.vite-plus/current/bin", "home/.vite-plus/bin"], comment = "Isolated legacy install layout with a fake managed Node runtime", snapshot = false },
{ argv = ["vpt", "cp", "$VP_HOME/current/bin/vp", "home/.vite-plus/current/bin/vp"], comment = "The isolated install's vp binary", snapshot = false },
{ argv = ["vpt", "cp", "$VP_HOME/current/bin/vp", "home/.vite-plus/bin/vp"], comment = "Marks the layout as a legacy install for detection", snapshot = false },
{ argv = ["vpt", "chmod", "+x", "home/.vite-plus/current/bin/vp"], snapshot = false },
{ argv = ["vpt", "chmod", "+x", "home/.vite-plus/bin/vp"], snapshot = false },
{ argv = ["vpt", "write-file", ".node-version", "22.18.0\n"], comment = "Project Node.js version", snapshot = false },
{ argv = ["vpt", "write-file", "home/js_runtime/node/22.18.0/bin/node", "#!/bin/sh\necho fake-node\n"], comment = "Fake node binary", snapshot = false },
{ argv = ["vpt", "chmod", "+x", "home/js_runtime/node/22.18.0/bin/node"], snapshot = false },
{ argv = ["vpt", "cp", "fake-corepack.sh", "home/js_runtime/node/22.18.0/bin/corepack"], comment = "Fake bundled corepack that echoes its args", snapshot = false },
{ argv = ["vpt", "chmod", "+x", "home/js_runtime/node/22.18.0/bin/corepack"], snapshot = false },
{ argv = ["vp", "env", "setup"], envs = [["VP_HOME", "${workspace}/home"]], comment = "Create shims in the isolated home", snapshot = false },
{ argv = ["corepack", "use", "pnpm@10"], envs = [["VP_HOME", "${workspace}/home"], ["PATH", "${workspace}/home/bin:${PATH}"]], comment = "Non-link commands run unchanged" },
{ argv = ["corepack", "enable", "--install-directory", "/tmp/custom-dir"], envs = [["VP_HOME", "${workspace}/home"], ["PATH", "${workspace}/home/bin:${PATH}"]], comment = "Explicit --install-directory is respected, clobbered npm shim is restored" },
{ argv = ["corepack", "enable"], envs = [["VP_HOME", "${workspace}/home"], ["PATH", "${workspace}/home/bin:${PATH}"]], comment = "--install-directory defaults to VP_HOME/bin" },
{ argv = ["vpt", "stat-file", "home/bin/npm", "--assert", "symlink"], comment = "Vite+ owns the npm shim" },
{ argv = ["vpt", "write-file", "home/.vite-plus/js_runtime/node/22.18.0/bin/node", "#!/bin/sh\necho fake-node\n"], comment = "Fake node binary", snapshot = false },
{ argv = ["vpt", "chmod", "+x", "home/.vite-plus/js_runtime/node/22.18.0/bin/node"], snapshot = false },
{ argv = ["vpt", "cp", "fake-corepack.sh", "home/.vite-plus/js_runtime/node/22.18.0/bin/corepack"], comment = "Fake bundled corepack that echoes its args", snapshot = false },
{ argv = ["vpt", "chmod", "+x", "home/.vite-plus/js_runtime/node/22.18.0/bin/corepack"], snapshot = false },
{ argv = ["./home/.vite-plus/current/bin/vp", "env", "setup"], comment = "Create shims in the isolated install (self-located, no VP_HOME)", snapshot = false },
{ argv = ["corepack", "use", "pnpm@10"], envs = [["PATH", "${workspace}/home/.vite-plus/bin:${PATH}"]], comment = "Non-link commands run unchanged" },
{ argv = ["corepack", "enable", "--install-directory", "/tmp/custom-dir"], envs = [["PATH", "${workspace}/home/.vite-plus/bin:${PATH}"]], comment = "Explicit --install-directory is respected, clobbered npm shim is restored" },
{ argv = ["corepack", "enable"], envs = [["PATH", "${workspace}/home/.vite-plus/bin:${PATH}"]], comment = "--install-directory defaults to the install's bin dir" },
{ argv = ["vpt", "stat-file", "home/.vite-plus/bin/npm", "--assert", "symlink"], comment = "Vite+ owns the npm shim" },
]
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
# shim_corepack_enable_install_directory

## `vpt mkdir -p home/js_runtime/node/22.18.0/bin`
## `vpt mkdir -p home/.vite-plus/js_runtime/node/22.18.0/bin home/.vite-plus/current/bin home/.vite-plus/bin`

Isolated VP_HOME with a fake managed Node runtime layout
Isolated legacy install layout with a fake managed Node runtime


## `vpt cp $VP_HOME/current/bin/vp home/.vite-plus/current/bin/vp`

The isolated install's vp binary


## `vpt cp $VP_HOME/current/bin/vp home/.vite-plus/bin/vp`

Marks the layout as a legacy install for detection


## `vpt chmod +x home/.vite-plus/current/bin/vp`


## `vpt chmod +x home/.vite-plus/bin/vp`


## `vpt write-file .node-version '22.18.0
Expand All @@ -11,59 +27,57 @@ Isolated VP_HOME with a fake managed Node runtime layout
Project Node.js version


## `vpt write-file home/js_runtime/node/22.18.0/bin/node '#'\!'/bin/sh
## `vpt write-file home/.vite-plus/js_runtime/node/22.18.0/bin/node '#'\!'/bin/sh
echo fake-node
'`

Fake node binary


## `vpt chmod +x home/js_runtime/node/22.18.0/bin/node`
## `vpt chmod +x home/.vite-plus/js_runtime/node/22.18.0/bin/node`


## `vpt cp fake-corepack.sh home/js_runtime/node/22.18.0/bin/corepack`
## `vpt cp fake-corepack.sh home/.vite-plus/js_runtime/node/22.18.0/bin/corepack`

Fake bundled corepack that echoes its args


## `vpt chmod +x home/js_runtime/node/22.18.0/bin/corepack`
## `vpt chmod +x home/.vite-plus/js_runtime/node/22.18.0/bin/corepack`


## `VP_HOME=${workspace}/home vp env setup`
## `./home/.vite-plus/current/bin/vp env setup`

Create shims in the isolated home
Create shims in the isolated install (self-located, no VP_HOME)


## `VP_HOME=${workspace}/home PATH=${workspace}/home/bin:${PATH} corepack use pnpm@10`
## `PATH=${workspace}/home/.vite-plus/bin:${PATH} corepack use pnpm@10`

Non-link commands run unchanged

```
corepack use pnpm@10
```

## `VP_HOME=${workspace}/home PATH=${workspace}/home/bin:${PATH} corepack enable --install-directory /tmp/custom-dir`
## `PATH=${workspace}/home/.vite-plus/bin:${PATH} corepack enable --install-directory /tmp/custom-dir`

Explicit --install-directory is respected, clobbered npm shim is restored

```
corepack enable --install-directory /tmp/custom-dir
warn: 'npm' is managed by Vite+ and was restored. Vite+ already resolves 'npm' per project, so corepack does not need to manage it.
```

## `VP_HOME=${workspace}/home PATH=${workspace}/home/bin:${PATH} corepack enable`
## `PATH=${workspace}/home/.vite-plus/bin:${PATH} corepack enable`

--install-directory defaults to VP_HOME/bin
--install-directory defaults to the install's bin dir

```
corepack enable --install-directory <root>/home/bin
warn: 'npm' is managed by Vite+ and was restored. Vite+ already resolves 'npm' per project, so corepack does not need to manage it.
corepack enable --install-directory <root>/home/.vite-plus/bin
```

## `vpt stat-file home/bin/npm --assert symlink`
## `vpt stat-file home/.vite-plus/bin/npm --assert symlink`

Vite+ owns the npm shim

```
home/bin/npm: symlink
home/.vite-plus/bin/npm: symlink
```
25 changes: 24 additions & 1 deletion crates/vp_cli_snapshots/tests/cli_snapshots/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,9 +525,27 @@ impl CaseHome {
if flavor == Flavor::Local {
self.write_local_package_cmd_shims(&package_dir, &local_bin_dir)?;
}
self.run_env_setup(&vp)?;

// Complete the legacy install shape (`bin/vp` alongside
// `current/bin/vp`) before any case CLI runs: layout detection
// classifies `<X>/current/bin/vp` as a split data dir unless
// `<X>/bin/vp` exists, and `vp env setup` below would otherwise
// write shims into the split bin dir instead of `<VP_HOME>/bin`.
let vp_bin_dir = self.vp_home().join("bin");
std::fs::create_dir_all(&vp_bin_dir)
.map_err(|e| format!("failed to create bin dir: {e}"))?;
#[cfg(unix)]
{
let link = vp_bin_dir.join(VP_BINARY_NAME);
let _ = std::fs::remove_file(&link);
std::os::unix::fs::symlink("../current/bin/vp", &link)
.map_err(|e| format!("failed to link bin/vp: {e}"))?;
}
#[cfg(windows)]
flavor::install_file(&vp_bin_dir.join(VP_BINARY_NAME), &runtime.global_vp, "bin/vp.exe")?;

self.run_env_setup(&vp)?;

let mut tool_dirs = match flavor {
Flavor::Global => vec![vp_bin_dir],
Flavor::Local => vec![local_bin_dir, vp_bin_dir],
Expand Down Expand Up @@ -625,6 +643,11 @@ impl CaseHome {
env.insert("TERM".into(), "xterm-256color".into());
env.insert("VP_CLI_TEST".into(), "1".into());
env.insert("NODE_NO_WARNINGS".into(), "1".into());
// The CLI no longer reads VP_HOME (the provisioned
// `<home>/.vite-plus/current/bin/vp` self-locates, and the on-disk
// `<home>/.vite-plus` selects the legacy layout). Kept because
// fixture steps reference `$VP_HOME/...` in `vpt` argv (expanded
// from this env by vpt's `expand_env_arg`).
env.insert("VP_HOME".into(), self.vp_home().into_os_string());
if cfg!(windows) {
env.insert("USERPROFILE".into(), self.home.clone().into_os_string());
Expand Down
20 changes: 11 additions & 9 deletions crates/vp_command/src/ps1_shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ use vt_powershell::{POWERSHELL_PREFIX, find_ps1_sibling, is_stdin_terminal, powe
/// - no `PowerShell` host (`pwsh.exe` or `powershell.exe`) is on PATH,
/// - stdin is not a terminal (the `.ps1` wrappers hang on piped/null
/// stdin and the Ctrl+C concern doesn't apply without a TTY),
/// - the resolved path is outside `$VP_HOME` (or `$VP_HOME` is
/// unresolvable) AND not under any `node_modules/.bin/`,
/// - the resolved path is outside the vite-plus install root
/// AND not under any `node_modules/.bin/`,
/// - the resolved path is not a `.cmd` (case-insensitive),
/// - the `.cmd` has no sibling `.ps1`.
#[must_use]
Expand All @@ -61,16 +61,18 @@ pub fn rewrite_cmd_to_powershell(
rewrite_in_scope(resolved, vp_home().map(AsRef::as_ref), host, is_stdin_terminal())
}

/// Cached `$VP_HOME` (`~/.vite-plus` by default; overridable via env var).
/// Returns `None` if `vp_shared::get_vp_home()` failed; the rewrite still
/// applies to `node_modules/.bin/*.cmd` paths in that case (the two scopes
/// are independent).
/// Cached vite-plus install root (`~/.vite-plus` under the legacy layout; the
/// data directory under the split layout).
///
/// The returned value is always `Some`; the `Option` only exists because the
/// rewrite scope check also applies to `node_modules/.bin/*.cmd` paths, which
/// are independent of the install root.
fn vp_home() -> Option<&'static AbsolutePathBuf> {
use std::sync::LazyLock;

static VP_HOME: LazyLock<Option<AbsolutePathBuf>> =
LazyLock::new(|| vp_shared::get_vp_home().ok());
VP_HOME.as_ref()
static INSTALL_ROOT: LazyLock<AbsolutePathBuf> =
LazyLock::new(|| vp_shared::Dirs::get().data_dir());
Some(&INSTALL_ROOT)
}

/// Pure rewrite logic. Factored out so tests can drive it on any platform
Expand Down
6 changes: 3 additions & 3 deletions crates/vp_global_cli/src/commands/env/bin_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use serde::{Deserialize, Serialize};
use vt_path::AbsolutePathBuf;

use super::config::get_vp_home;
use crate::error::Error;

/// Source that installed a binary.
Expand Down Expand Up @@ -52,9 +51,10 @@ impl BinConfig {
Self { name, package, version: String::new(), node_version, source: BinSource::Npm }
}

/// Get the bins directory path (~/.vite-plus/bins/).
/// Get the bins directory path (`<data>/bins/`; `~/.vite-plus/bins/` under
/// the legacy layout — identical on disk).
pub fn bins_dir() -> Result<AbsolutePathBuf, Error> {
Ok(get_vp_home()?.join("bins"))
Ok(vp_shared::Dirs::get().bins_dir())
}

/// Get the path to a binary's config file.
Expand Down
Loading
Loading