Skip to content

feat(test): upgrade to Vitest v5 with migration support #1523

feat(test): upgrade to Vitest v5 with migration support

feat(test): upgrade to Vitest v5 with migration support #1523

name: Test Standalone Install Scripts
permissions: {}
on:
workflow_dispatch:
pull_request:
paths:
- 'packages/cli/install.sh'
- 'packages/cli/install.ps1'
- 'packages/cli/install-legacy.sh'
- 'packages/cli/install-legacy.ps1'
- 'packages/tools/src/install-global-cli.ts'
- 'packages/tools/src/local-npm-registry.ts'
- 'crates/vp_installer/**'
- 'crates/vp_trampoline/**'
- 'crates/vp_global_cli/**'
- 'crates/vp_shared/**'
- 'crates/vp_pm_cli/**'
- 'crates/vp_setup/**'
- '.github/workflows/test-standalone-install.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
test-install-sh:
name: Test install.sh (${{ matrix.name }})
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
name: Linux x64 glibc
- os: macos-15-intel
name: macOS x64
- os: macos-latest
name: macOS ARM64
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- name: Pin VP_HOME for pre-split published CLI
# This job installs the published CLI, which does not support the split
# layout. Set VP_HOME so the new installer writes a compatible
# single-root tree.
run: echo "VP_HOME=$HOME/.vite-plus" >> $GITHUB_ENV
- name: Check bootstrap handoff
run: bash .github/scripts/test-install-bootstrap.sh
- name: Run install.sh
run: cat packages/cli/install.sh | VP_LEGACY_INSTALLER_URL="file://$PWD/packages/cli/install-legacy.sh" bash
- name: Verify installation
working-directory: ${{ runner.temp }}
run: |
# Source shell config to get PATH updated
if [ -f ~/.zshenv ]; then
# non-interactive shells use zshenv
source ~/.zshenv
elif [ -f ~/.zshrc ]; then
# interactive shells use zshrc
source ~/.zshrc
elif [ -f ~/.bash_profile ]; then
# non-interactive shells use bash_profile
source ~/.bash_profile
elif [ -f ~/.bashrc ]; then
# interactive shells use bashrc
source ~/.bashrc
else
export PATH="$HOME/.vite-plus/bin:$PATH"
fi
echo "PATH: $PATH"
ls -al ~/
vp --version
vp --help
# test create command
vp create vite --no-interactive --no-agent -- hello --no-interactive -t vanilla
cd hello && vp run build && vp --version
- name: Set PATH
shell: bash
run: |
echo "$HOME/.vite-plus/bin" >> $GITHUB_PATH
- name: Verify bin setup
run: |
# Verify bin directory was created by vp env --setup
BIN_PATH="$HOME/.vite-plus/bin"
ls -al "$BIN_PATH"
if [ ! -d "$BIN_PATH" ]; then
echo "Error: Bin directory not found: $BIN_PATH"
exit 1
fi
# Verify shim executables exist
for shim in node npm npx; do
if [ ! -f "$BIN_PATH/$shim" ]; then
echo "Error: Shim not found: $BIN_PATH/$shim"
exit 1
fi
echo "Found shim: $BIN_PATH/$shim"
done
# Verify vp env doctor works
vp env doctor
vp env run --node 24 -- node -p "process.versions"
which node
which npm
which npx
which vp
- name: Verify upgrade
run: |
# --check queries npm registry and prints update status
vp upgrade --check
vp upgrade 0.1.14-alpha.1
vp --version
# rollback to the previous version (should succeed after a real update)
vp upgrade --rollback
vp --version
test-install-sh-layout:
name: Test install.sh layout (fresh split + grandfather)
runs-on: ubuntu-latest
permissions:
contents: read
env:
VP_LOCAL_BINARY: ${{ github.workspace }}/target/release/vp
VP_SKIP_DEPS_INSTALL: '1'
VP_NODE_MANAGER: 'yes'
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- uses: ./.github/actions/clone
- uses: oxc-project/setup-rust@68c3199c5339f965e6e163924c3c450773eba42b # main (pending v1.0.17 — Swatinem/rust-cache v2.9.1 for node24)
- name: Shell config paths preserve metacharacters
run: |
# Sourced installer functions use these globals and literal
# metacharacters. Keep them unchanged.
# shellcheck disable=SC2016,SC2034
set -euo pipefail
sudo apt-get update
sudo apt-get install --no-install-recommends -y fish zsh
# Load installer helpers without invoking main.
eval "$(sed '/^main "[$]@"$/d' packages/cli/install-legacy.sh)"
TEST_ROOT=$(mktemp -d)
export HOME="$TEST_ROOT/home"
export USERPROFILE='C:/Users/alice'
export XDG_CONFIG_HOME="$TEST_ROOT/shell-config"
mkdir -p "$HOME" "$XDG_CONFIG_HOME"
# Windows layout resolution uses USERPROFILE first. Shell source
# entries can use short paths only for the shell HOME.
is_windows_uname() { return 0; }
unset VP_HOME
use_legacy_layout
test "$CONFIG_DIR" = "$USERPROFILE/.vite-plus"
test "$CONFIG_DIR_REF_POSIX" = "$CONFIG_DIR"
test "$CONFIG_DIR_REF_FISH" = "$CONFIG_DIR"
test "$CONFIG_DIR_REF_NU" = "$CONFIG_DIR"
fake_vp() {
printf 'bin\t%s/bin\n' "$USERPROFILE"
printf 'data\t%s/AppData/Local/vite-plus/data\n' "$USERPROFILE"
printf 'cache\t%s/AppData/Local/vite-plus/cache\n' "$USERPROFILE"
printf 'config\t%s/AppData/Roaming/vite-plus\n' "$USERPROFILE"
printf 'state\t%s/AppData/Local/vite-plus/state\n' "$USERPROFILE"
}
apply_dirs_from_vp fake_vp
test "$CONFIG_DIR_REF_POSIX" = "$CONFIG_DIR"
test "$CONFIG_DIR_REF_FISH" = "$CONFIG_DIR"
test "$CONFIG_DIR_REF_NU" = "$CONFIG_DIR"
export USERPROFILE="$HOME"
SPECIAL=$(printf 'config-$USER-"-\047-`-\134')
CONFIG_DIR="$TEST_ROOT/$SPECIAL"
mkdir -p "$CONFIG_DIR"
set_config_dir_refs "$CONFIG_DIR" "$HOME"
SHELL_CONFIG_SUMMARY=()
SHELL_CONFIG_FAILED_SHELLS=()
SHELL_CONFIG_HAS_UPDATED=false
SHELL_CONFIG_HAS_CONFIGURED=false
SHELL_CONFIG_HAS_FAILURE=false
printf '%s\n' 'export VP_CONFIG_SOURCE_MARKER=bash-zsh' > "$CONFIG_DIR/env"
touch "$HOME/.bashrc"
configure_bash_path
configure_zsh_path
bash --noprofile --norc -c 'source "$HOME/.bashrc"; test "$VP_CONFIG_SOURCE_MARKER" = bash-zsh'
zsh -f -c 'source "$HOME/.zshenv"; test "$VP_CONFIG_SOURCE_MARKER" = bash-zsh'
printf '%s\n' 'set -gx VP_CONFIG_SOURCE_MARKER fish' > "$CONFIG_DIR/env.fish"
configure_fish_path
fish --no-config -c 'source "$XDG_CONFIG_HOME/fish/conf.d/vite-plus.fish"; test "$VP_CONFIG_SOURCE_MARKER" = fish'
NUSHELL_DIR="$TEST_ROOT/nushell"
discover_nushell_vendor_autoload_dir() { printf '%s\n' "$NUSHELL_DIR"; }
configure_nushell_path
python3 - "$NUSHELL_DIR/vite-plus.nu" "$CONFIG_DIR/env.nu" <<'PY'
import json
import pathlib
import sys
line = pathlib.Path(sys.argv[1]).read_text().splitlines()[1]
assert line.startswith('source '), line
assert json.loads(line.removeprefix('source ')) == sys.argv[2], line
PY
- name: Build local vp
run: cargo build --release -p vp_global_cli
- name: Shell installer rejects an unversioned Node sidecar
run: |
set -euo pipefail
eval "$(sed '/^main "[$]@"$/d' packages/cli/install-legacy.sh)"
TEST_ROOT=$(mktemp -d)
TEST_BIN="$TEST_ROOT/bin"
INSTALL_DIR="$TEST_ROOT/data"
mkdir -p "$TEST_BIN"
touch "$TEST_BIN/node.exe" "$TEST_ROOT/vp.exe"
printf '%s\n' "$INSTALL_DIR" > "$TEST_BIN/node.shim"
if is_vite_plus_node_shim "$TEST_BIN" "$TEST_ROOT/vp.exe"; then
echo "install.sh accepted an unversioned node.shim"
exit 1
fi
- name: CI hides shell file warnings
run: |
set -euo pipefail
eval "$(sed '/^main "[$]@"$/d' packages/cli/install-legacy.sh)"
SHELL_CONFIG=$(mktemp)
chmod 444 "$SHELL_CONFIG"
CI=true
RESULT=0
OUTPUT=$(append_source_to_file "$SHELL_CONFIG" '. "$HOME/.vite-plus/env"' 2>&1) || RESULT=$?
test "$RESULT" -eq 3
test -z "$OUTPUT"
VP_LOG=trace
RESULT=0
OUTPUT=$(append_source_to_file "$SHELL_CONFIG" '. "$HOME/.vite-plus/env"' 2>&1) || RESULT=$?
test "$RESULT" -eq 3
echo "$OUTPUT" | grep -F "trace"
echo "$OUTPUT" | grep -F "permission denied"
CI=false
unset VP_LOG
RESULT=0
OUTPUT=$(append_source_to_file "$SHELL_CONFIG" '. "$HOME/.vite-plus/env"' 2>&1) || RESULT=$?
test "$RESULT" -eq 3
echo "$OUTPUT" | grep -F "warn"
echo "$OUTPUT" | grep -F "permission denied"
- name: Incomplete directory overrides are rejected
run: |
set -euo pipefail
OUTPUT=$(mktemp)
if (
unset VP_HOME VP_BIN_DIR VP_CACHE_DIR
export VP_DATA_DIR="$RUNNER_TEMP/incomplete-vp-data"
bash packages/cli/install.sh
) >"$OUTPUT" 2>&1; then
cat "$OUTPUT"
echo "install.sh accepted an incomplete directory override group"
exit 1
fi
cat "$OUTPUT"
grep -F "Set VP_BIN_DIR, VP_DATA_DIR, and VP_CACHE_DIR together" "$OUTPUT"
- name: Existing GitHub setup-vp uses legacy layout
run: |
set -euo pipefail
LEGACY=$(mktemp -d)
FAKE_TGZ=$(mktemp)
export HOME="$LEGACY"
export USERPROFILE="$LEGACY"
export GITHUB_ACTION_REPOSITORY=voidzero-dev/setup-vp
unset VP_VPDIRS_AWARE
unset VP_HOME VP_BIN_DIR VP_DATA_DIR VP_CACHE_DIR
unset XDG_DATA_HOME XDG_CACHE_HOME XDG_CONFIG_HOME XDG_STATE_HOME
VP_LOCAL_TGZ="$FAKE_TGZ" VP_VERSION=local-setup-vp-legacy bash packages/cli/install.sh
test -e "$LEGACY/.vite-plus/current"
test -e "$LEGACY/.vite-plus/bin/vp"
test ! -d "$LEGACY/.local/share/vite-plus"
test "$(readlink "$LEGACY/.vite-plus/bin/vp")" = "$LEGACY/.vite-plus/current/bin/vp"
"$LEGACY/.vite-plus/bin/vp" --version
- name: Existing GitLab setup-vp uses legacy layout
run: |
set -euo pipefail
LEGACY=$(mktemp -d)
FAKE_TGZ=$(mktemp)
export HOME="$LEGACY"
export USERPROFILE="$LEGACY"
unset GITHUB_ACTION_REPOSITORY
export GITLAB_CI=true
export SETUP_VP_SETUP_REF=v1.15.0
unset VP_VPDIRS_AWARE
unset VP_HOME VP_BIN_DIR VP_DATA_DIR VP_CACHE_DIR
unset XDG_DATA_HOME XDG_CACHE_HOME XDG_CONFIG_HOME XDG_STATE_HOME
VP_LOCAL_TGZ="$FAKE_TGZ" VP_VERSION=local-setup-vp-gitlab-legacy bash packages/cli/install.sh
test -e "$LEGACY/.vite-plus/current"
test -e "$LEGACY/.vite-plus/bin/vp"
test ! -d "$LEGACY/.local/share/vite-plus"
test "$(readlink "$LEGACY/.vite-plus/bin/vp")" = "$LEGACY/.vite-plus/current/bin/vp"
"$LEGACY/.vite-plus/bin/vp" --version
- name: Fresh home uses split layout
run: |
set -euo pipefail
FRESH="$(mktemp -d)/home with 'quotes' and \$dollar"
mkdir -p "$FRESH"
FAKE_TGZ=$(mktemp)
export HOME="$FRESH"
export USERPROFILE="$FRESH"
export GITHUB_ACTION_REPOSITORY=voidzero-dev/setup-vp
export VP_VPDIRS_AWARE=1
unset VP_HOME VP_BIN_DIR VP_DATA_DIR VP_CACHE_DIR
unset XDG_DATA_HOME XDG_CACHE_HOME XDG_CONFIG_HOME XDG_STATE_HOME
# setup-vp sources the installer and reads SHIM_DIR in its own shell.
VP_LOCAL_TGZ="$FAKE_TGZ" VP_VERSION=local-test source packages/cli/install.sh
test "$SHIM_DIR" = "$FRESH/.local/share/vite-plus/bin"
test ! -d "$FRESH/.vite-plus"
test -e "$FRESH/.local/share/vite-plus/current"
test -e "$FRESH/.local/share/vite-plus/bin/vp"
test -f "$FRESH/.config/vite-plus/env"
test "$(readlink "$FRESH/.local/share/vite-plus/bin/vp")" = "$FRESH/.local/share/vite-plus/current/bin/vp"
"$FRESH/.local/share/vite-plus/bin/vp" --version
DUMP=$(VP_DUMP_DIRS=1 "$FRESH/.local/share/vite-plus/bin/vp")
dump_dir() { printf '%s\n' "$DUMP" | awk -F'\t' -v key="$1" '$1 == key { print $2; exit }'; }
test "$(dump_dir bin)" = "$FRESH/.local/share/vite-plus/bin"
test "$(dump_dir data)" = "$FRESH/.local/share/vite-plus"
test "$(dump_dir cache)" = "$FRESH/.cache/vite-plus"
test "$(dump_dir config)" = "$FRESH/.config/vite-plus"
test "$(dump_dir state)" = "$FRESH/.local/state/vite-plus"
test "$INSTALL_DIR" = "$(dump_dir data)"
test "$SHIM_DIR" = "$(dump_dir bin)"
test "$CACHE_DIR" = "$(dump_dir cache)"
test "$CONFIG_DIR" = "$(dump_dir config)"
test "$STATE_DIR" = "$(dump_dir state)"
- name: Custom shared bin preserves an unrelated Node executable
run: |
set -euo pipefail
FOREIGN=$(mktemp -d)
FAKE_TGZ=$(mktemp)
mkdir -p "$FOREIGN/.local/bin"
printf '#!/bin/sh\necho foreign-node\n' > "$FOREIGN/.local/bin/node"
chmod +x "$FOREIGN/.local/bin/node"
export HOME="$FOREIGN"
export USERPROFILE="$FOREIGN"
export GITHUB_ACTION_REPOSITORY=voidzero-dev/setup-vp
unset VP_VPDIRS_AWARE
export VP_BIN_DIR="$FOREIGN/.local/bin"
export VP_DATA_DIR="$FOREIGN/.local/share/vite-plus"
export VP_CACHE_DIR="$FOREIGN/.cache/vite-plus"
# Override the setting for this job. A custom bin can be shared. CI
# must not replace a Node executable that Vite+ does not own.
export VP_NODE_MANAGER=
unset VP_HOME
unset XDG_DATA_HOME XDG_CACHE_HOME XDG_CONFIG_HOME XDG_STATE_HOME
VP_LOCAL_TGZ="$FAKE_TGZ" VP_VERSION=local-foreign-node bash packages/cli/install.sh
test ! -d "$FOREIGN/.vite-plus"
test -f "$FOREIGN/.local/bin/node"
test ! -L "$FOREIGN/.local/bin/node"
test "$("$FOREIGN/.local/bin/node")" = "foreign-node"
- name: Existing ~/.vite-plus is reused
run: |
set -euo pipefail
GRAND=$(mktemp -d)
FAKE_TGZ=$(mktemp)
# A prior install must contain a `current` link. An empty
# ~/.vite-plus directory is not an install.
mkdir -p "$GRAND/.vite-plus/0.0.1/bin"
ln -s 0.0.1 "$GRAND/.vite-plus/current"
echo keep > "$GRAND/.vite-plus/.keep"
export HOME="$GRAND"
export USERPROFILE="$GRAND"
unset VP_HOME VP_BIN_DIR VP_DATA_DIR VP_CACHE_DIR
unset XDG_DATA_HOME XDG_CACHE_HOME XDG_CONFIG_HOME XDG_STATE_HOME
VP_LOCAL_TGZ="$FAKE_TGZ" VP_VERSION=local-test bash packages/cli/install.sh
test -f "$GRAND/.vite-plus/.keep"
test -e "$GRAND/.vite-plus/current"
test -e "$GRAND/.vite-plus/bin/vp"
test ! -d "$GRAND/.local/share/vite-plus"
test "$(readlink "$GRAND/.vite-plus/bin/vp")" = "$GRAND/.vite-plus/current/bin/vp"
"$GRAND/.vite-plus/bin/vp" --version
- name: Stray ~/.vite-plus does not capture a split install
run: |
set -euo pipefail
STRAY=$(mktemp -d)
FAKE_TGZ=$(mktemp)
export HOME="$STRAY"
export USERPROFILE="$STRAY"
unset VP_HOME VP_BIN_DIR VP_DATA_DIR VP_CACHE_DIR
unset XDG_DATA_HOME XDG_CACHE_HOME XDG_CONFIG_HOME XDG_STATE_HOME
VP_LOCAL_TGZ="$FAKE_TGZ" VP_VERSION=local-test bash packages/cli/install.sh
# A local pre-split Vite+ can create ~/.vite-plus for caches, config,
# and managed runtimes. This tree has no `current` link, so it is not
# an install.
mkdir -p "$STRAY/.vite-plus/cache" "$STRAY/.vite-plus/js_runtime"
echo '{}' > "$STRAY/.vite-plus/config.json"
# Resolution must use the split roots with and without the environment
# script variables.
data_root() { VP_DUMP_DIRS=1 "$STRAY/.local/share/vite-plus/bin/vp" | awk -F'\t' '$1=="data"{print $2}'; }
test "$(data_root)" = "$STRAY/.local/share/vite-plus"
test "$(VP_DATA_DIR="$STRAY/.local/share/vite-plus" VP_BIN_DIR="$STRAY/.local/bin" VP_CACHE_DIR="$STRAY/.cache/vite-plus" data_root)" = "$STRAY/.local/share/vite-plus"
# A reinstall must continue to use the split roots. It must not change
# the stray tree. `vp upgrade` uses the same resolution chain.
VP_LOCAL_TGZ="$FAKE_TGZ" VP_VERSION=local-test2 bash packages/cli/install.sh
test -x "$STRAY/.local/share/vite-plus/local-test2/bin/vp"
test "$(readlink "$STRAY/.local/share/vite-plus/current")" = "local-test2"
test ! -e "$STRAY/.vite-plus/current"
test ! -e "$STRAY/.vite-plus/bin"
"$STRAY/.local/share/vite-plus/bin/vp" --version
test-install-sh-old-version:
name: Test install.sh pre-split release (${{ matrix.name }})
runs-on: ${{ matrix.os }}
permissions:
contents: read
env:
VP_VERSION: '0.2.9'
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
name: Linux x64 glibc
- os: macos-latest
name: macOS ARM64
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- name: Run install.sh for a pre-split release
# Do not set VP_HOME. A pre-VpDirs release must select the monolithic
# ~/.vite-plus root.
run: |
cat packages/cli/install.sh | VP_LEGACY_INSTALLER_URL="file://$PWD/packages/cli/install-legacy.sh" bash 2>&1 | tee install-output.txt
grep -F "does not support the split directory layout" install-output.txt
- name: Verify monolithic layout
run: |
test -x "$HOME/.vite-plus/$VP_VERSION/bin/vp"
test -e "$HOME/.vite-plus/current"
test -f "$HOME/.vite-plus/env"
# No split-layout leftovers.
test ! -d "$HOME/.local/share/vite-plus"
test ! -d "$HOME/.config/vite-plus"
test ! -e "$HOME/.local/bin/vp"
ls -al "$HOME/.vite-plus/bin"
for shim in vp vpr vpx node npm npx corepack; do
test -e "$HOME/.vite-plus/bin/$shim"
done
- name: Verify PATH-resolved commands work
run: |
export PATH="$HOME/.vite-plus/bin:$PATH"
command -v vp | grep -F "$HOME/.vite-plus/bin/vp"
vp --version
node --version
npm --version
vp env doctor
test-install-ps1-old-version:
name: Test install.ps1 pre-split release (Windows x64)
# On a GitHub-hosted runner, %USERPROFILE% matches the operating-system
# profile. The old resolver and `vp env doctor` must report the install path.
runs-on: windows-latest
permissions:
contents: read
env:
VP_VERSION: '0.2.9'
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- name: Run install.ps1 for a pre-split release
# Do not set VP_HOME. A pre-VpDirs release must select the monolithic
# %USERPROFILE%\.vite-plus root.
shell: pwsh
run: |
# *>&1 adds the Write-Host information stream to the pipeline.
# Tee-Object can then record installer notices.
& ./packages/cli/install.ps1 *>&1 | Tee-Object -FilePath install-output.txt
if (-not (Select-String -Path install-output.txt -Pattern "does not support the split directory layout" -SimpleMatch -Quiet)) {
Write-Error "Expected the pre-split fallback notice in installer output"
exit 1
}
if (-not (Select-String -Path install-output.txt -Pattern "Install locations:" -SimpleMatch -Quiet)) {
Write-Error "Expected install locations in installer output"
exit 1
}
if (-not (Select-String -Path install-output.txt -Pattern "Data directory: ~\.vite-plus" -SimpleMatch -Quiet)) {
Write-Error "Expected the single-root data directory in installer output"
exit 1
}
if (-not (Select-String -Path install-output.txt -Pattern "Bin directory: ~\.vite-plus\bin" -SimpleMatch -Quiet)) {
Write-Error "Expected the single-root bin directory in installer output"
exit 1
}
- name: Verify monolithic layout
shell: pwsh
run: |
$root = Join-Path $env:USERPROFILE ".vite-plus"
if (-not (Test-Path "$root\$env:VP_VERSION\bin\vp.exe")) { Write-Error "payload missing"; exit 1 }
if (-not (Test-Path "$root\current")) { Write-Error "current link missing"; exit 1 }
# No split-layout leftovers.
foreach ($split in @((Join-Path $env:LOCALAPPDATA "vite-plus"), (Join-Path $env:APPDATA "vite-plus"))) {
if (Test-Path $split) { Write-Error "unexpected split dir: $split"; exit 1 }
}
Get-ChildItem -Force "$root\bin"
foreach ($shim in @("vp.exe", "vpr.exe", "vpx.exe", "node.exe", "npm.exe", "npx.exe")) {
if (-not (Test-Path "$root\bin\$shim")) { Write-Error "shim missing: $shim"; exit 1 }
}
- name: Verify PATH-resolved commands work
shell: pwsh
run: |
$env:Path = "$env:USERPROFILE\.vite-plus\bin;$env:Path"
where.exe vp
vp --version
node --version
npm --version
vp env doctor
test-install-sh-readonly-config:
name: Test install.sh (readonly shell config)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- name: Pin VP_HOME for pre-split published CLI
run: echo "VP_HOME=$HOME/.vite-plus" >> $GITHUB_ENV
- name: Make shell config files read-only
run: |
# Simulate Nix-managed or read-only shell configs
touch ~/.bashrc ~/.bash_profile ~/.profile
chmod 444 ~/.bashrc ~/.bash_profile ~/.profile
- name: Run install.sh
run: |
output=$(cat packages/cli/install.sh | VP_LEGACY_INSTALLER_URL="file://$PWD/packages/cli/install-legacy.sh" bash 2>&1) || {
echo "$output"
echo "Install script exited with non-zero status"
exit 1
}
echo "$output"
# Verify installation succeeds (not a fatal error)
echo "$output" | grep -q "successfully installed"
# Verify the success message shows the single-root locations
echo "$output" | grep -q "Install locations:"
echo "$output" | grep -q "Data directory: ~/.vite-plus"
echo "$output" | grep -q "Bin directory: ~/.vite-plus/bin"
# CI gets PATH from the runner environment.
# VP_LOG=trace shows shell file errors.
# Normal output does not show these errors.
if echo "$output" | grep -Eqi "permission denied|Shell configuration:|Some shells still need manual setup"; then
echo "Installer printed shell configuration details in CI"
exit 1
fi
- name: Verify vp works via direct path
run: |
~/.vite-plus/bin/vp --version
test-install-sh-arm64:
name: Test install.sh (Linux ARM64 glibc via QEMU)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- name: Set up QEMU
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0
with:
platforms: arm64
- name: Run install.sh in ARM64 container
run: |
docker run --rm --platform linux/arm64 \
-v "${{ github.workspace }}:/workspace" \
ubuntu:20.04 bash -c "
ls -al ~/
apt-get update && apt-get install -y curl ca-certificates
export VP_HOME=\"\$HOME/.vite-plus\"
cat /workspace/packages/cli/install.sh | VP_LEGACY_INSTALLER_URL=file:///workspace/packages/cli/install-legacy.sh bash
if [ -f ~/.profile ]; then
source ~/.profile
elif [ -f ~/.bashrc ]; then
source ~/.bashrc
else
export PATH="$HOME/.vite-plus/bin:$PATH"
fi
vp --version
vp --help
vp dlx print-current-version
# Verify bin setup
BIN_PATH=\"\$HOME/.vite-plus/bin\"
if [ ! -d \"\$BIN_PATH\" ]; then
echo \"Error: Bin directory not found: \$BIN_PATH\"
exit 1
fi
for shim in node npm npx; do
if [ ! -f \"\$BIN_PATH/\$shim\" ]; then
echo \"Error: Shim not found: \$BIN_PATH/\$shim\"
exit 1
fi
echo \"Found shim: \$BIN_PATH/\$shim\"
done
vp env doctor
export VP_LOG=trace
vp env run --node 24 -- node -p \"process.versions\"
# Verify upgrade
vp upgrade --check
vp upgrade 0.1.14-alpha.1
vp --version
vp upgrade --rollback
vp --version
# FIXME: qemu: uncaught target signal 11 (Segmentation fault) - core dumped
# vp create vite --no-interactive --no-agent -- hello --no-interactive -t vanilla
# cd hello && vp run build
"
test-install-sh-musl-x64:
name: Test install.sh (Linux x64 musl)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- name: Run install.sh in Alpine container
run: |
docker run --rm \
-v "${{ github.workspace }}:/workspace" \
-e CI=true \
alpine:3.21 sh -c "
# libstdc++: required by unofficial-builds Node.js musl binary
apk add --no-cache bash curl ca-certificates libstdc++
export VP_HOME=\"\$HOME/.vite-plus\"
cat /workspace/packages/cli/install.sh | VP_LEGACY_INSTALLER_URL=file:///workspace/packages/cli/install-legacy.sh bash
export PATH=\"\$HOME/.vite-plus/bin:\$PATH\"
vp --version
vp --help
vp dlx print-current-version
# Verify bin setup
BIN_PATH=\"\$HOME/.vite-plus/bin\"
if [ ! -d \"\$BIN_PATH\" ]; then
echo \"Error: Bin directory not found: \$BIN_PATH\"
exit 1
fi
for shim in node npm npx; do
if [ ! -f \"\$BIN_PATH/\$shim\" ]; then
echo \"Error: Shim not found: \$BIN_PATH/\$shim\"
exit 1
fi
echo \"Found shim: \$BIN_PATH/\$shim\"
done
vp env doctor
vp env run --node 24 -- node -p \"process.versions\"
# Test create command
vp create vite --no-interactive --no-agent -- hello --no-interactive -t vanilla
cd hello && vp run build && vp --version
cd ..
# Verify upgrade
vp upgrade --check
vp upgrade 0.1.14-alpha.1
vp --version
vp upgrade --rollback
vp --version
"
test-install-sh-musl-arm64:
name: Test install.sh (Linux ARM64 musl via QEMU)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- name: Set up QEMU
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0
with:
platforms: arm64
- name: Run install.sh in ARM64 Alpine container
run: |
docker run --rm --platform linux/arm64 \
-v "${{ github.workspace }}:/workspace" \
-e CI=true \
alpine:3.21 sh -c "
# libstdc++ is needed by unofficial-builds Node.js musl binary
apk add --no-cache bash curl ca-certificates libstdc++
export VP_HOME=\"\$HOME/.vite-plus\"
cat /workspace/packages/cli/install.sh | VP_LEGACY_INSTALLER_URL=file:///workspace/packages/cli/install-legacy.sh bash
export PATH=\"\$HOME/.vite-plus/bin:\$PATH\"
vp --version
vp --help
vp dlx print-current-version
# Verify bin setup
BIN_PATH=\"\$HOME/.vite-plus/bin\"
if [ ! -d \"\$BIN_PATH\" ]; then
echo \"Error: Bin directory not found: \$BIN_PATH\"
exit 1
fi
for shim in node npm npx; do
if [ ! -f \"\$BIN_PATH/\$shim\" ]; then
echo \"Error: Shim not found: \$BIN_PATH/\$shim\"
exit 1
fi
echo \"Found shim: \$BIN_PATH/\$shim\"
done
vp env doctor
export VP_LOG=trace
vp env run --node 24 -- node -p \"process.versions\"
# FIXME: QEMU doesn't support all syscalls needed by rolldown/tsdown
# vp create vite --no-interactive --no-agent -- hello --no-interactive -t vanilla
# cd hello && vp run build && vp --version
# Verify upgrade
vp upgrade --check
vp upgrade 0.1.14-alpha.1
vp --version
vp upgrade --rollback
vp --version
"
test-install-ps1-v5:
name: Test install.ps1 (Windows x64, PowerShell 5.1)
runs-on: namespace-profile-windows-4c-8g
permissions:
contents: read
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- name: Pin VP_HOME to USERPROFILE
# Namespace's Windows runners run jobs under a service account whose real
# profile (C:\Windows\system32\config\systemprofile) differs from
# %USERPROFILE%; vp resolves its home from the OS profile. Pin VP_HOME so
# the install, generated shims, and these assertions share one location.
shell: bash
run: echo "VP_HOME=$USERPROFILE\.vite-plus" >> $GITHUB_ENV
- name: Assert PowerShell 5.x
shell: powershell
run: |
Write-Host "PowerShell version: $($PSVersionTable.PSVersion)"
if ($PSVersionTable.PSVersion.Major -ne 5) {
Write-Error "Expected PowerShell 5.x but got $($PSVersionTable.PSVersion)"
exit 1
}
- name: Check bootstrap handoff
shell: powershell
run: ./.github/scripts/test-install-bootstrap.ps1
- name: Run install.ps1
shell: powershell
run: |
& ./packages/cli/install.ps1
- name: Run install.ps1 via irm simulation (catches BOM issues)
shell: powershell
# Piped installers cannot locate a sibling script; use this PR's legacy implementation.
env:
VP_LEGACY_INSTALLER_URL: https://raw.githubusercontent.com/${{ github.event.pull_request.head.repo.full_name || github.repository }}/${{ github.event.pull_request.head.sha || github.sha }}/packages/cli/install-legacy.ps1
run: |
$ErrorActionPreference = "Stop"
Get-Content ./packages/cli/install.ps1 -Raw | Invoke-Expression
- name: Set PATH
shell: bash
run: |
echo "$USERPROFILE\.vite-plus\bin" >> $GITHUB_PATH
- name: Verify installation
shell: powershell
working-directory: ${{ runner.temp }}
run: |
Write-Host "PATH: $env:Path"
vp --version
vp --help
vp create vite --no-interactive --no-agent -- hello --no-interactive -t vanilla
cd hello
vp run build
vp --version
- name: Verify bin setup
shell: powershell
run: |
$binPath = "$env:USERPROFILE\.vite-plus\bin"
Get-ChildItem -Force $binPath
if (-not (Test-Path $binPath)) {
Write-Error "Bin directory not found: $binPath"
exit 1
}
$expectedShims = @("node.exe", "npm.exe", "npx.exe")
foreach ($shim in $expectedShims) {
$shimFile = Join-Path $binPath $shim
if (-not (Test-Path $shimFile)) {
Write-Error "Shim not found: $shimFile"
exit 1
}
Write-Host "Found shim: $shimFile"
}
where.exe node
where.exe npm
where.exe npx
where.exe vp
$env:Path = "$env:USERPROFILE\.vite-plus\bin;$env:Path"
vp env doctor
vp env run --node 24 -- node -p "process.versions"
- name: Verify upgrade
shell: powershell
run: |
vp upgrade --check
vp upgrade 0.1.14-alpha.1
vp --version
vp upgrade --rollback
vp --version
test-install-ps1-arm64:
name: Test install.ps1 (Windows ARM64)
runs-on: windows-11-arm
permissions:
contents: read
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- name: Pin VP_HOME for pre-split published CLI
shell: bash
run: echo "VP_HOME=$USERPROFILE\.vite-plus" >> $GITHUB_ENV
- name: Run install.ps1
shell: pwsh
run: |
& ./packages/cli/install.ps1
- name: Set PATH
shell: bash
run: |
echo "$USERPROFILE\.vite-plus\bin" >> $GITHUB_PATH
- name: Verify installation
shell: pwsh
working-directory: ${{ runner.temp }}
run: |
Write-Host "PATH: $env:Path"
vp --version
vp --help
vp create vite --no-interactive --no-agent -- hello --no-interactive -t vanilla
cd hello
vp run build
vp --version
- name: Verify bin setup
shell: pwsh
run: |
$binPath = "$env:USERPROFILE\.vite-plus\bin"
Get-ChildItem -Force $binPath
if (-not (Test-Path $binPath)) {
Write-Error "Bin directory not found: $binPath"
exit 1
}
$expectedShims = @("node.exe", "npm.exe", "npx.exe")
foreach ($shim in $expectedShims) {
$shimFile = Join-Path $binPath $shim
if (-not (Test-Path $shimFile)) {
Write-Error "Shim not found: $shimFile"
exit 1
}
Write-Host "Found shim: $shimFile"
}
where.exe node
where.exe npm
where.exe npx
where.exe vp
$env:Path = "$env:USERPROFILE\.vite-plus\bin;$env:Path"
vp env doctor
vp env run --node 24 -- node -p "process.versions"
- name: Verify upgrade
shell: pwsh
run: |
vp upgrade --check
vp upgrade 0.1.14-alpha.1
vp --version
vp upgrade --rollback
vp --version
test-install-ps1-v76:
name: Test install.ps1 (Windows x64, PowerShell 7.6)
runs-on: namespace-profile-windows-4c-8g
permissions:
contents: read
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- name: Pin VP_HOME to USERPROFILE
# Namespace's Windows runners run jobs under a service account whose real
# profile (C:\Windows\system32\config\systemprofile) differs from
# %USERPROFILE%; vp resolves its home from the OS profile. Pin VP_HOME so
# the install, generated shims, and these assertions share one location.
shell: bash
run: echo "VP_HOME=$USERPROFILE\.vite-plus" >> $GITHUB_ENV
- name: Install PowerShell 7.6
shell: pwsh
run: |
# --tool-path (not --global): a global tool install resolves the target
# under the OS user profile, which is the service-account systemprofile
# on Namespace, not %USERPROFILE%. An explicit path is account-independent.
dotnet tool install --tool-path "$env:USERPROFILE\.dotnet\tools" PowerShell --version 7.6.0
$pwsh76 = "$env:USERPROFILE\.dotnet\tools\pwsh.exe"
echo "PWSH76=$pwsh76" >> $env:GITHUB_ENV
$ver = & $pwsh76 -NoProfile -Command '$PSVersionTable.PSVersion.ToString()'
Write-Host "PowerShell version: $ver"
if (-not $ver.StartsWith("7.6")) {
Write-Error "Expected PowerShell 7.6.x but got $ver"
exit 1
}
- name: Run install.ps1 via iex under PowerShell 7.6
shell: pwsh
# Piped installers cannot locate a sibling script; use this PR's legacy implementation.
env:
VP_LEGACY_INSTALLER_URL: https://raw.githubusercontent.com/${{ github.event.pull_request.head.repo.full_name || github.repository }}/${{ github.event.pull_request.head.sha || github.sha }}/packages/cli/install-legacy.ps1
run: |
& $env:PWSH76 -NoProfile -Command "Get-Content ./packages/cli/install.ps1 -Raw | Invoke-Expression"
- name: Set PATH
shell: bash
run: |
echo "$USERPROFILE\.vite-plus\bin" >> $GITHUB_PATH
- name: Verify installation
shell: pwsh
working-directory: ${{ runner.temp }}
run: |
Write-Host "PATH: $env:Path"
vp --version
vp --help
vp create vite --no-interactive --no-agent -- hello --no-interactive -t vanilla
cd hello
vp run build
vp --version
- name: Verify bin setup
shell: pwsh
run: |
$binPath = "$env:USERPROFILE\.vite-plus\bin"
Get-ChildItem -Force $binPath
if (-not (Test-Path $binPath)) {
Write-Error "Bin directory not found: $binPath"
exit 1
}
$expectedShims = @("node.exe", "npm.exe", "npx.exe")
foreach ($shim in $expectedShims) {
$shimFile = Join-Path $binPath $shim
if (-not (Test-Path $shimFile)) {
Write-Error "Shim not found: $shimFile"
exit 1
}
Write-Host "Found shim: $shimFile"
}
where.exe node
where.exe npm
where.exe npx
where.exe vp
$env:Path = "$env:USERPROFILE\.vite-plus\bin;$env:Path"
vp env doctor
vp env run --node 24 -- node -p "process.versions"
- name: Verify upgrade
shell: pwsh
run: |
vp upgrade --check
vp upgrade 0.1.14-alpha.1
vp --version
vp upgrade --rollback
vp --version
test-install-ps1-release-age:
name: Test install.ps1 (minimum-release-age)
runs-on: namespace-profile-windows-4c-8g
permissions:
contents: read
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- name: Verify minimumReleaseAge blocks non-interactive install
shell: powershell
run: |
$ErrorActionPreference = "Stop"
$env:CI = "true"
$env:VP_NODE_MANAGER = "no"
$env:VP_HOME = Join-Path $env:RUNNER_TEMP "vite-plus-release-age"
$npmrc = Join-Path $env:USERPROFILE ".npmrc"
$backup = Join-Path $env:RUNNER_TEMP ".npmrc.backup"
if (Test-Path $npmrc) {
Move-Item -Path $npmrc -Destination $backup -Force
}
try {
Set-Content -Path $npmrc -Value "minimum-release-age=10000000"
$output = & powershell -NoProfile -ExecutionPolicy Bypass -File .\packages\cli\install.ps1 2>&1
$exitCode = $LASTEXITCODE
$text = $output -join "`n"
Write-Host $text
if ($exitCode -eq 0) {
Write-Error "Expected install.ps1 to fail when pnpm minimum-release-age blocks vite-plus"
exit 1
}
if ($text -notmatch "Install blocked by your minimumReleaseAge setting") {
Write-Error "Expected release-age block message in installer output"
exit 1
}
if ($text -notmatch "ERR_PNPM_NO_MATURE_MATCHING_VERSION|minimumReleaseAge") {
Write-Error "Expected pnpm release-age details in installer output"
exit 1
}
$installLog = Get-ChildItem -Path $env:VP_HOME -Recurse -Filter install.log | Select-Object -First 1
if (-not $installLog) {
Write-Error "Expected install.log to be written under VP_HOME"
exit 1
}
$logText = Get-Content -Path $installLog.FullName -Raw
if ($logText -notmatch "ERR_PNPM_NO_MATURE_MATCHING_VERSION|minimumReleaseAge") {
Write-Error "Expected pnpm release-age details in install.log"
exit 1
}
$overrideFiles = Get-ChildItem -Path $env:VP_HOME -Recurse -Force -Filter .npmrc |
Where-Object { $_.FullName -notmatch "\\js_runtime\\" }
if ($overrideFiles) {
Write-Host "Unexpected override files:"
$overrideFiles | ForEach-Object { Write-Host $_.FullName }
Write-Error "Non-interactive install must not write minimum-release-age overrides"
exit 1
}
# The child install.ps1 is expected to fail in this test. Reset the
# native command exit code so the GitHub Actions PowerShell wrapper
# does not fail the step after our assertions pass.
$global:LASTEXITCODE = 0
} finally {
Remove-Item -Path $npmrc -Force -ErrorAction SilentlyContinue
if (Test-Path $backup) {
Move-Item -Path $backup -Destination $npmrc -Force
}
}
test-install-ps1:
name: Test install.ps1 (Windows x64)
# Stays on GitHub-hosted windows-latest, NOT the Namespace Windows runner.
# This job runs `vp env doctor` under bash `set -e`, and doctor compares vp's
# resolved home against PATH. Namespace's Windows runners execute jobs under a
# Session-0 service account whose real profile is
# C:\Windows\system32\config\systemprofile, not %USERPROFILE%, so even with
# VP_HOME set the installed vp resolves the systemprofile home and doctor
# fails. The other install jobs pass on Namespace because they don't run
# `vp env doctor` under `set -e`.
runs-on: windows-latest
permissions:
contents: read
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- name: Pin VP_HOME for pre-split published CLI
shell: bash
run: echo "VP_HOME=$USERPROFILE\.vite-plus" >> $GITHUB_ENV
- name: Run install.ps1
shell: pwsh
run: |
& ./packages/cli/install.ps1
- name: Set PATH
shell: bash
run: |
echo "$USERPROFILE\.vite-plus\bin" >> $GITHUB_PATH
- name: Verify upgrade
shell: pwsh
run: |
# --check queries npm registry and prints update status
vp upgrade --check
vp upgrade 0.1.14-alpha.1
vp --version
# rollback to the previous version (should succeed after a real update)
vp upgrade --rollback
vp --version
- name: Verify installation on powershell
shell: pwsh
working-directory: ${{ runner.temp }}
run: |
# Print PATH from environment
echo "PATH: $env:Path"
vp --version
vp --help
# $env:VP_LOG = "trace"
# test create command
vp create vite --no-interactive --no-agent -- hello --no-interactive -t vanilla
cd hello && vp run build && vp --version
- name: Verify bin setup on powershell
shell: pwsh
run: |
# Verify bin directory was created by vp env --setup
$binPath = "$env:USERPROFILE\.vite-plus\bin"
Get-ChildItem -Force $binPath
if (-not (Test-Path $binPath)) {
Write-Error "Bin directory not found: $binPath"
exit 1
}
# Verify shim executables exist (trampoline .exe files on Windows)
$expectedShims = @("node.exe", "npm.exe", "npx.exe")
foreach ($shim in $expectedShims) {
$shimFile = Join-Path $binPath $shim
if (-not (Test-Path $shimFile)) {
Write-Error "Shim not found: $shimFile"
exit 1
}
Write-Host "Found shim: $shimFile"
}
where.exe node
where.exe npm
where.exe npx
where.exe vp
# Verify vp env doctor works
$env:Path = "$env:USERPROFILE\.vite-plus\bin;$env:Path"
vp env doctor
vp env run --node 24 -- node -p "process.versions"
- name: Verify installation on cmd
shell: cmd
working-directory: ${{ runner.temp }}
run: |
echo PATH: %PATH%
dir "%USERPROFILE%\.vite-plus"
dir "%USERPROFILE%\.vite-plus\bin"
REM test create command
vp create vite --no-interactive --no-agent -- hello-cmd --no-interactive -t vanilla
cd hello-cmd && vp run build && vp --version
- name: Verify bin setup on cmd
shell: cmd
run: |
REM Verify bin directory was created by vp env --setup
set "BIN_PATH=%USERPROFILE%\.vite-plus\bin"
dir "%BIN_PATH%"
REM Verify shim executables exist (Windows uses trampoline .exe files)
for %%s in (node.exe npm.exe npx.exe vp.exe) do (
if not exist "%BIN_PATH%\%%s" (
echo Error: Shim not found: %BIN_PATH%\%%s
exit /b 1
)
echo Found shim: %BIN_PATH%\%%s
)
where node
where npm
where npx
where vp
REM Verify vp env doctor works
vp env doctor
vp env run --node 24 -- node -p "process.versions"
- name: Verify installation on bash
shell: bash
working-directory: ${{ runner.temp }}
run: |
echo "PATH: $PATH"
ls -al ~/.vite-plus
ls -al ~/.vite-plus/bin
vp --version
vp --help
# test create command
vp create vite --no-interactive --no-agent -- hello-bash --no-interactive -t vanilla
cd hello-bash && vp run build && vp --version
- name: Verify bin setup on bash
shell: bash
run: |
# Verify bin directory was created by vp env --setup
BIN_PATH="$HOME/.vite-plus/bin"
ls -al "$BIN_PATH"
if [ ! -d "$BIN_PATH" ]; then
echo "Error: Bin directory not found: $BIN_PATH"
exit 1
fi
# Verify trampoline .exe files exist
for shim in node.exe npm.exe npx.exe vp.exe; do
if [ ! -f "$BIN_PATH/$shim" ]; then
echo "Error: Trampoline shim not found: $BIN_PATH/$shim"
exit 1
fi
echo "Found trampoline shim: $BIN_PATH/$shim"
done
# Verify vp env doctor works
vp env doctor
vp env run --node 24 -- node -p "process.versions"
which node
which npm
which npx
which vp
test-vp-setup-exe:
name: Test vp-setup.exe (pwsh)
runs-on: namespace-profile-windows-4c-8g
permissions:
contents: read
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- uses: ./.github/actions/clone
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
- name: Pin VP_HOME to USERPROFILE
# Namespace's Windows runners run jobs under a service account whose real
# profile (C:\Windows\system32\config\systemprofile) differs from
# %USERPROFILE%; vp-setup.exe installs into VP_HOME (falling back to the OS
# profile). Pin it so the install and these assertions share one location.
shell: bash
run: echo "VP_HOME=$USERPROFILE\.vite-plus" >> $GITHUB_ENV
- name: Setup Dev Drive
uses: samypr100/setup-dev-drive@562171fe8df7401fdf582d766cd3b6ab80d5b1a0 # v4.1.0
with:
drive-size: 12GB
drive-format: ReFS
env-mapping: |
CARGO_HOME,{{ DEV_DRIVE }}/.cargo
RUSTUP_HOME,{{ DEV_DRIVE }}/.rustup
- uses: oxc-project/setup-rust@68c3199c5339f965e6e163924c3c450773eba42b # main (pending v1.0.17 — Swatinem/rust-cache v2.9.1 for node24)
with:
target-dir: ${{ format('{0}/target', env.DEV_DRIVE) }}
- name: Build Windows installers
shell: bash
run: |
cargo build --release -p vp_global_cli -p vp_installer
node packages/tools/src/build-trampoline.ts --release
- name: Test trampoline with an extended-length payload path
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$root = Join-Path $env:RUNNER_TEMP "vp-trampoline-long-payload"
$bin = Join-Path $root "bin"
$data = Join-Path $root "data"
$cache = Join-Path $root "cache"
$segment = "segment-" + ("x" * 60)
while ((Join-Path $data "current\bin\vp.exe").Length -le 300) {
$data = Join-Path $data $segment
}
$payloadBin = Join-Path $data "current\bin"
$payload = Join-Path $payloadBin "vp.exe"
if ($payload.Length -le 260) {
throw "The payload path must be longer than MAX_PATH: $payload"
}
Remove-Item -Recurse -Force $root -ErrorAction SilentlyContinue
[System.IO.Directory]::CreateDirectory($bin) | Out-Null
[System.IO.Directory]::CreateDirectory($payloadBin) | Out-Null
[System.IO.File]::Copy(
(Join-Path $env:DEV_DRIVE "target/release/vp-shim.exe"),
(Join-Path $bin "vp.exe"),
$true
)
[System.IO.File]::Copy(
(Join-Path $env:DEV_DRIVE "target/release/vp.exe"),
$payload,
$true
)
# This fixture represents an installed payload, not a first-launch installer.
[System.IO.File]::WriteAllText((Join-Path $payloadBin ".vp-setup-complete"), "")
$pointer = "vite-plus-shim-v1`nlayout=split`ndata=$data`ncache=$cache`n"
[System.IO.File]::WriteAllText((Join-Path $bin "vp.shim"), $pointer)
$output = (& (Join-Path $bin "vp.exe") --version 2>&1) | Out-String
$exitCode = $LASTEXITCODE
Write-Host $output
if ($exitCode -ne 0) {
throw "The trampoline exited with $exitCode for payload path $payload"
}
- name: vp-setup.exe rejects invalid directory overrides
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $false
$installer = Join-Path $env:DEV_DRIVE "target/release/vp-setup.exe"
$root = Join-Path $env:RUNNER_TEMP "vp-setup-invalid-dirs"
$defaultRoot = Join-Path $env:LOCALAPPDATA "vite-plus"
$defaultRootExisted = Test-Path $defaultRoot
$dirVars = @("VP_HOME", "VP_BIN_DIR", "VP_DATA_DIR", "VP_CACHE_DIR")
$togetherError = "Set all three variables together: VP_BIN_DIR, VP_DATA_DIR, and VP_CACHE_DIR"
$cases = @(
@{ Name = "bin"; Values = @{ VP_BIN_DIR = Join-Path $root "bin" }; Error = $togetherError }
@{ Name = "data"; Values = @{ VP_DATA_DIR = Join-Path $root "data" }; Error = $togetherError }
@{ Name = "cache"; Values = @{ VP_CACHE_DIR = Join-Path $root "cache" }; Error = $togetherError }
@{ Name = "bin-data"; Values = @{ VP_BIN_DIR = Join-Path $root "bin"; VP_DATA_DIR = Join-Path $root "data" }; Error = $togetherError }
@{ Name = "bin-cache"; Values = @{ VP_BIN_DIR = Join-Path $root "bin"; VP_CACHE_DIR = Join-Path $root "cache" }; Error = $togetherError }
@{ Name = "data-cache"; Values = @{ VP_DATA_DIR = Join-Path $root "data"; VP_CACHE_DIR = Join-Path $root "cache" }; Error = $togetherError }
@{ Name = "relative-home"; Values = @{ VP_HOME = "relative-home" }; Error = "Set VP_HOME to an absolute path" }
@{ Name = "relative-split"; Values = @{ VP_BIN_DIR = "relative-bin"; VP_DATA_DIR = "relative-data"; VP_CACHE_DIR = "relative-cache" }; Error = "Set VP_BIN_DIR to an absolute path" }
)
foreach ($case in $cases) {
foreach ($name in $dirVars) {
[Environment]::SetEnvironmentVariable($name, $null)
}
foreach ($entry in $case.Values.GetEnumerator()) {
[Environment]::SetEnvironmentVariable($entry.Key, $entry.Value)
}
$output = (& $installer --yes --quiet --no-node-manager --no-modify-path 2>&1) | Out-String
$exitCode = $LASTEXITCODE
Write-Host $output
if ($exitCode -ne 1) {
throw "$($case.Name) exited with $exitCode, expected 1"
}
if (-not $output.Contains($case.Error)) {
throw "$($case.Name) did not report '$($case.Error)'"
}
foreach ($value in $case.Values.Values) {
if ([System.IO.Path]::IsPathRooted($value) -and (Test-Path $value)) {
throw "$($case.Name) created requested root $value"
}
}
if (-not $defaultRootExisted -and (Test-Path $defaultRoot)) {
throw "$($case.Name) created default root $defaultRoot"
}
}
# Each installer process must fail in this test. Reset the native exit
# code after the assertions. This lets the PowerShell step succeed.
$global:LASTEXITCODE = 0
- name: vp-setup.exe rejects releases before 0.3.0
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $false
$installer = Join-Path $env:DEV_DRIVE "target/release/vp-setup.exe"
$installRoot = Join-Path $env:RUNNER_TEMP "vp-setup-unsupported-version"
Remove-Item -Recurse -Force $installRoot -ErrorAction SilentlyContinue
$env:VP_HOME = $installRoot
Remove-Item Env:VP_BIN_DIR, Env:VP_DATA_DIR, Env:VP_CACHE_DIR -ErrorAction SilentlyContinue
$output = (& $installer --yes --quiet --version 0.2.9 --no-node-manager --no-modify-path 2>&1) | Out-String
$exitCode = $LASTEXITCODE
Write-Host $output
if ($exitCode -ne 1) {
throw "vp-setup.exe exited with $exitCode, expected 1"
}
if (-not $output.Contains("Install vite-plus 0.3.0 or later")) {
throw "vp-setup.exe did not report its minimum supported version"
}
if ($output -match "(?i)preview") {
throw "vp-setup.exe mentioned preview builds"
}
if (Test-Path $installRoot) {
throw "vp-setup.exe created an installation root for version 0.2.9: $installRoot"
}
$global:LASTEXITCODE = 0
- name: Incomplete directory overrides are rejected in PowerShell
shell: pwsh
run: |
Remove-Item Env:VP_HOME, Env:VP_BIN_DIR, Env:VP_CACHE_DIR -ErrorAction SilentlyContinue
$env:VP_DATA_DIR = Join-Path $env:RUNNER_TEMP "incomplete-vp-data"
$stdout = Join-Path $env:RUNNER_TEMP "vp-incomplete-override.stdout.txt"
$stderr = Join-Path $env:RUNNER_TEMP "vp-incomplete-override.stderr.txt"
$installer = (Resolve-Path ./packages/cli/install.ps1).Path
$process = Start-Process -FilePath (Get-Command pwsh).Source `
-ArgumentList @("-NoProfile", "-File", "`"$installer`"") `
-RedirectStandardOutput $stdout -RedirectStandardError $stderr `
-Wait -PassThru
$text = @(
Get-Content -LiteralPath $stdout -Raw -ErrorAction SilentlyContinue
Get-Content -LiteralPath $stderr -Raw -ErrorAction SilentlyContinue
) -join [Environment]::NewLine
Write-Host $text
if ($process.ExitCode -ne 1) {
throw "install.ps1 exited with $($process.ExitCode), expected 1"
}
if (-not $text.Contains("Set VP_BIN_DIR, VP_DATA_DIR, and VP_CACHE_DIR together")) {
throw "install.ps1 did not report the incomplete override group"
}
- name: Existing setup-vp uses legacy layout in PowerShell installer
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$root = Join-Path $env:RUNNER_TEMP "vite-plus-setup-vp-legacy-ps1"
$fakeTgz = Join-Path $root "vite-plus.tgz"
Remove-Item -Recurse -Force $root -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $root | Out-Null
New-Item -ItemType File -Force -Path $fakeTgz | Out-Null
$env:USERPROFILE = $root
$env:GITHUB_ACTION_REPOSITORY = "voidzero-dev/setup-vp"
Remove-Item Env:VP_VPDIRS_AWARE -ErrorAction SilentlyContinue
Remove-Item Env:VP_HOME -ErrorAction SilentlyContinue
Remove-Item Env:VP_BIN_DIR -ErrorAction SilentlyContinue
Remove-Item Env:VP_DATA_DIR -ErrorAction SilentlyContinue
Remove-Item Env:VP_CACHE_DIR -ErrorAction SilentlyContinue
$env:VP_LOCAL_BINARY = Join-Path $env:DEV_DRIVE "target/release/vp.exe"
$env:VP_LOCAL_TGZ = $fakeTgz
$env:VP_SKIP_DEPS_INSTALL = "1"
$env:VP_VERSION = "local-setup-vp-legacy"
$env:VP_NODE_MANAGER = "no"
$env:CI = "true"
& ./packages/cli/install.ps1
$legacyRoot = Join-Path $root ".vite-plus"
$vp = Join-Path $legacyRoot "bin/vp.exe"
if (-not (Test-Path (Join-Path $legacyRoot "current"))) {
Write-Error "setup-vp compatibility install did not use $legacyRoot"
}
if (-not (Test-Path $vp)) {
Write-Error "setup-vp compatibility entrypoint not found: $vp"
}
& $vp --version
$longDirectory = Join-Path $legacyRoot "current\node_modules\.pnpm"
$segment = "package-" + ("x" * 60)
while ((Join-Path $longDirectory "payload.txt").Length -le 300) {
$longDirectory = Join-Path $longDirectory $segment
}
$longFile = Join-Path $longDirectory "payload.txt"
[System.IO.Directory]::CreateDirectory($longDirectory) | Out-Null
[System.IO.File]::WriteAllText($longFile, "long-path")
if ($longFile.Length -le 260) { throw "test path is not longer than MAX_PATH: $longFile" }
$env:VP_HOME = $legacyRoot
& $vp implode --yes
for ($i = 0; $i -lt 150; $i++) {
$removingRoots = @(Get-ChildItem -LiteralPath $root -Filter ".vite-plus.removing-*" -Force -ErrorAction SilentlyContinue)
if ($removingRoots.Count -eq 0) { break }
Start-Sleep -Milliseconds 100
}
if (Test-Path $legacyRoot) { throw "implode left the monolithic root" }
if ($removingRoots.Count -ne 0) { throw "implode left a renamed monolithic root" }
- name: PowerShell installer rejects an unversioned Node sidecar
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$root = Join-Path $env:RUNNER_TEMP "vite-plus-foreign-node-ps1"
$data = Join-Path $root "data"
$bin = Join-Path $root "bin"
$cache = Join-Path $root "cache"
$node = Join-Path $bin "node.exe"
$pointer = Join-Path $bin "node.shim"
$fakeTgz = Join-Path $root "vite-plus.tgz"
Remove-Item -Recurse -Force $root -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $bin | Out-Null
[System.IO.File]::WriteAllText($node, "foreign-node")
[System.IO.File]::WriteAllText($pointer, "$data`n")
New-Item -ItemType File -Force -Path $fakeTgz | Out-Null
Remove-Item Env:VP_HOME -ErrorAction SilentlyContinue
$env:VP_BIN_DIR = $bin
$env:VP_DATA_DIR = $data
$env:VP_CACHE_DIR = $cache
$env:VP_LOCAL_BINARY = Join-Path $env:DEV_DRIVE "target/release/vp.exe"
$env:VP_LOCAL_TGZ = $fakeTgz
$env:VP_SKIP_DEPS_INSTALL = "1"
$env:VP_VERSION = "local-foreign-node"
$env:VP_NODE_MANAGER = ""
$env:CI = "true"
& ./packages/cli/install.ps1
if ([System.IO.File]::ReadAllText($node) -ne "foreign-node") {
Write-Error "install.ps1 replaced an unrelated node.exe"
}
if ([System.IO.File]::ReadAllText($pointer) -ne "$data`n") {
Write-Error "install.ps1 changed the unversioned node.shim"
}
- name: Complete directory overrides keep the split layout through the trampoline
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$root = Join-Path $env:RUNNER_TEMP "vp-complete-overrides 'quoted'"
$env:USERPROFILE = Join-Path $root "profile"
$env:LOCALAPPDATA = Join-Path $env:USERPROFILE "AppData\Local"
$env:APPDATA = Join-Path $env:USERPROFILE "AppData\Roaming"
$env:VP_DATA_DIR = Join-Path $root "data only"
$env:VP_BIN_DIR = Join-Path $env:VP_DATA_DIR "bin"
$env:VP_CACHE_DIR = Join-Path $root "cache"
$env:VP_LOCAL_BINARY = Join-Path $env:DEV_DRIVE "target/release/vp.exe"
$env:VP_LOCAL_TGZ = Join-Path $root "empty.tgz"
$env:VP_SKIP_DEPS_INSTALL = "1"
$env:VP_VERSION = "local-complete-overrides"
$env:VP_NODE_MANAGER = "no"
Remove-Item Env:VP_HOME -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $root | Out-Null
New-Item -ItemType File -Force -Path $env:VP_LOCAL_TGZ | Out-Null
$output = (. ./packages/cli/install.ps1 *>&1) | Out-String
Write-Host $output
if (-not $output.Contains("Vite+ setup complete.")) {
throw "install.ps1 did not complete self-setup"
}
if ($script:ShimDir -ne $env:VP_BIN_DIR) {
throw "The sourced installer did not report its bin directory"
}
function Get-DirMap([string]$VpBinary) {
$env:VP_DUMP_DIRS = "1"
try {
$map = @{}
foreach ($line in @(& $VpBinary)) {
$parts = "$line" -split "`t", 2
if ($parts.Length -eq 2) { $map[$parts[0]] = $parts[1] }
}
return $map
} finally {
Remove-Item Env:VP_DUMP_DIRS -ErrorAction SilentlyContinue
}
}
$payload = Join-Path $env:VP_DATA_DIR "current\bin\vp.exe"
$trampoline = Join-Path $env:VP_DATA_DIR "bin\vp.exe"
$expected = Get-DirMap $payload
$reported = @{
data = $script:InstallDir; bin = $script:ShimDir; cache = $script:CacheDir
config = $script:ConfigDir; state = $script:StateDir
}
foreach ($key in $reported.Keys) {
if ($reported[$key] -ne $expected[$key]) { throw "Installer reported an incorrect $key directory" }
}
if ($expected['layout'] -ne 'split') { throw "payload did not select split layout" }
if ($expected['bin'] -ne (Join-Path $env:VP_DATA_DIR 'bin')) { throw "unexpected bin root" }
if ($expected['cache'] -ne $env:VP_CACHE_DIR) { throw "unexpected cache root" }
Remove-Item Env:VP_BIN_DIR, Env:VP_DATA_DIR, Env:VP_CACHE_DIR -ErrorAction Stop
$actual = Get-DirMap $trampoline
foreach ($key in @('layout', 'data', 'bin', 'cache', 'config', 'state')) {
if ($actual[$key] -ne $expected[$key]) {
throw "$key differs: payload='$($expected[$key])', trampoline='$($actual[$key])'"
}
}
- name: Windows PowerShell 5.1 sources env.ps1 from a Unicode path
shell: powershell
run: |
$ErrorActionPreference = "Stop"
if ($PSVersionTable.PSVersion.Major -ne 5) {
throw "This test requires Windows PowerShell 5.1"
}
$suffix = "unicode-{0}{1}-{2}{3}" -f [char]0x6D4B, [char]0x8BD5, [char]0x8DEF, [char]0x5F84
$root = Join-Path $env:RUNNER_TEMP "vp-unicode-env"
$env:VP_HOME = Join-Path $root $suffix
$env:VP_LOCAL_BINARY = Join-Path $env:DEV_DRIVE "target/release/vp.exe"
$env:VP_LOCAL_TGZ = Join-Path $root "empty.tgz"
$env:VP_SKIP_DEPS_INSTALL = "1"
$env:VP_VERSION = "local-unicode-env"
$env:VP_NODE_MANAGER = "yes"
$env:VP_NODE_VERSION = "22.13.1"
$env:CI = "true"
Remove-Item Env:VP_BIN_DIR -ErrorAction SilentlyContinue
Remove-Item Env:VP_DATA_DIR -ErrorAction SilentlyContinue
Remove-Item Env:VP_CACHE_DIR -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force $root -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $root | Out-Null
New-Item -ItemType File -Force -Path $env:VP_LOCAL_TGZ | Out-Null
$pathBeforeInstall = $env:Path
& ./packages/cli/install.ps1
$expectedHome = $env:VP_HOME
$expectedBin = Join-Path $expectedHome "bin"
$envFile = Join-Path $expectedHome "env.ps1"
$bom = [byte[]](0xEF, 0xBB, 0xBF)
function Assert-One-Utf8Bom([string]$Path) {
$bytes = [System.IO.File]::ReadAllBytes($Path)
if ($bytes.Length -lt 3 -or $bytes[0] -ne $bom[0] -or $bytes[1] -ne $bom[1] -or $bytes[2] -ne $bom[2]) {
throw "$Path does not start with a UTF-8 BOM"
}
if ($bytes.Length -ge 6 -and $bytes[3] -eq $bom[0] -and $bytes[4] -eq $bom[1] -and $bytes[5] -eq $bom[2]) {
throw "$Path starts with more than one UTF-8 BOM"
}
}
Assert-One-Utf8Bom $envFile
foreach ($name in @("env", "env.fish", "env.nu")) {
$bytes = [System.IO.File]::ReadAllBytes((Join-Path $expectedHome $name))
if ($bytes.Length -ge 3 -and $bytes[0] -eq $bom[0] -and $bytes[1] -eq $bom[1] -and $bytes[2] -eq $bom[2]) {
throw "$name has an unexpected UTF-8 BOM"
}
}
# Remove the PATH change made by the installer. The generated file
# must restore the exact Unicode path by itself.
$env:Path = $pathBeforeInstall
$env:VP_HOME = "incorrect-before-source"
. $envFile
if ($env:VP_HOME -cne $expectedHome) { throw "env.ps1 changed the Unicode VP_HOME" }
if ($__vp_bin -cne $expectedBin) { throw "env.ps1 changed the Unicode bin path" }
foreach ($name in @("vp", "node", "npm")) {
$expected = Join-Path $expectedBin "$name.exe"
$actual = (Get-Command "$name.exe" -CommandType Application | Select-Object -First 1).Source
if ($actual -cne $expected) { throw "$name resolves to '$actual' instead of '$expected'" }
}
vp --version
node --version
npm --version
vp env setup --env-only
Assert-One-Utf8Bom $envFile
$env:VP_TEST_ENV_FILE = $envFile
$env:VP_TEST_EXPECTED_HOME = $expectedHome
& pwsh -NoProfile -Command '. $env:VP_TEST_ENV_FILE; if ($env:VP_HOME -cne $env:VP_TEST_EXPECTED_HOME) { throw "PowerShell 7 changed VP_HOME" }; vp --version'
if ($LASTEXITCODE -ne 0) { throw "PowerShell 7 could not source env.ps1" }
- name: Split-layout implode removes long paths and the running trampoline safely
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$root = Join-Path $env:RUNNER_TEMP "vp-split-implode"
$data = Join-Path $root "data"
$bin = Join-Path $root "bin"
$cache = Join-Path $root "cache"
$fakeTgz = Join-Path $root "empty.tgz"
Remove-Item -Recurse -Force $root -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $root | Out-Null
New-Item -ItemType File -Force -Path $fakeTgz | Out-Null
Remove-Item Env:VP_HOME -ErrorAction SilentlyContinue
$env:VP_BIN_DIR = $bin
$env:VP_DATA_DIR = $data
$env:VP_CACHE_DIR = $cache
$env:VP_LOCAL_BINARY = Join-Path $env:DEV_DRIVE "target/release/vp.exe"
$env:VP_LOCAL_TGZ = $fakeTgz
$env:VP_SKIP_DEPS_INSTALL = "1"
$env:VP_VERSION = "local-split-implode"
$env:VP_NODE_MANAGER = "no"
& ./packages/cli/install.ps1
$vp = Join-Path $bin "vp.exe"
$sidecar = Join-Path $bin "vp.shim"
$unrelated = Join-Path $bin "unrelated.txt"
[System.IO.File]::WriteAllText($unrelated, "keep")
$longDirectory = Join-Path $data "current\node_modules\.pnpm"
$segment = "package-" + ("x" * 60)
while ((Join-Path $longDirectory "payload.txt").Length -le 300) {
$longDirectory = Join-Path $longDirectory $segment
}
$longFile = Join-Path $longDirectory "payload.txt"
[System.IO.Directory]::CreateDirectory($longDirectory) | Out-Null
[System.IO.File]::WriteAllText($longFile, "long-path")
if ($longFile.Length -le 260) { throw "test path is not longer than MAX_PATH: $longFile" }
& $vp implode --yes
for ($i = 0; $i -lt 150; $i++) {
$removingRoots = @(Get-ChildItem -LiteralPath $root -Filter "data.removing-*" -Force -ErrorAction SilentlyContinue)
if (-not (Test-Path $vp) -and -not (Test-Path $sidecar) -and $removingRoots.Count -eq 0) {
break
}
Start-Sleep -Milliseconds 100
}
if (Test-Path $vp) { throw "implode left vp.exe in the split bin" }
if (Test-Path $sidecar) { throw "implode left vp.shim in the split bin" }
if ($removingRoots.Count -ne 0) { throw "implode left a renamed split data root" }
if ([System.IO.File]::ReadAllText($unrelated) -ne "keep") {
throw "implode changed an unrelated bin entry"
}
# Reinstall as soon as a second implode returns. The deferred cleanup
# for the old trampoline must not remove the replacement.
& ./packages/cli/install.ps1
& $vp implode --yes
& ./packages/cli/install.ps1
for ($i = 0; $i -lt 150; $i++) {
$removingRoots = @(Get-ChildItem -LiteralPath $root -Filter "data.removing-*" -Force -ErrorAction SilentlyContinue)
if ($removingRoots.Count -eq 0) { break }
Start-Sleep -Milliseconds 100
}
if (-not (Test-Path $vp)) { throw "deferred cleanup removed the new vp.exe" }
if (-not (Test-Path $sidecar)) { throw "deferred cleanup removed the new vp.shim" }
if ($removingRoots.Count -ne 0) { throw "implode left a renamed split data root after reinstall" }
if ([System.IO.File]::ReadAllText($unrelated) -ne "keep") {
throw "the reinstall changed an unrelated bin entry"
}
& $vp --version
- name: Start local registry for vp-setup.exe
shell: bash
run: |
# Self-setup installs the version compiled into the downloaded binary.
test_version=$(sed -n 's/^version = "\(.*\)"/\1/p' crates/vp_global_cli/Cargo.toml | head -1)
registry_root="$RUNNER_TEMP/vp-setup-local-registry"
packages_dir="$registry_root/packages"
main_dir="$registry_root/vite-plus"
platform_dir="$registry_root/vite-plus-cli-win32-x64-msvc"
rm -rf "$registry_root"
mkdir -p "$packages_dir" "$main_dir" "$platform_dir"
node -e '
const fs = require("node:fs");
const path = require("node:path");
const [dir, version] = process.argv.slice(1);
fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({ name: "vite-plus", version }));
' "$main_dir" "$test_version"
node -e '
const fs = require("node:fs");
const path = require("node:path");
const [dir, version] = process.argv.slice(1);
const manifest = {
name: "@voidzero-dev/vite-plus-cli-win32-x64-msvc",
version,
files: ["vp.exe", "vp-shim.exe"],
};
fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify(manifest));
' "$platform_dir" "$test_version"
cp "$DEV_DRIVE/target/release/vp.exe" "$DEV_DRIVE/target/release/vp-shim.exe" "$platform_dir/"
npm pack "$main_dir" --pack-destination "$packages_dir"
npm pack "$platform_dir" --pack-destination "$packages_dir"
registry_log="$registry_root/registry.out"
node "$GITHUB_WORKSPACE/packages/tools/src/local-npm-registry.ts" --serve --packages-dir "$packages_dir" > "$registry_log" 2>&1 &
server_pid=$!
until grep -q '"registry"' "$registry_log" 2>/dev/null; do
if ! kill -0 "$server_pid" 2>/dev/null; then
cat "$registry_log"
exit 1
fi
sleep 0.2
done
handshake=$(grep -m1 '"registry"' "$registry_log")
registry=$(echo "$handshake" | node -e 'process.stdin.on("data", data => process.stdout.write(JSON.parse(data).registry))')
echo "VP_SETUP_TEST_REGISTRY=$registry" >> "$GITHUB_ENV"
echo "VP_SETUP_TEST_VERSION=$test_version" >> "$GITHUB_ENV"
- name: Create a dangling current junction
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$current = Join-Path $env:VP_HOME "current"
$removedTarget = Join-Path $env:RUNNER_TEMP "vp-setup-removed-target"
Remove-Item -Recurse -Force $env:VP_HOME, $removedTarget -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $env:VP_HOME, $removedTarget | Out-Null
& cmd.exe /d /c "mklink /J `"$current`" `"$removedTarget`""
if ($LASTEXITCODE -ne 0) {
throw "cmd.exe could not create the test junction"
}
Remove-Item -Recurse -Force $removedTarget
& fsutil.exe reparsepoint query $current | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "the dangling current entry is not a reparse point"
}
- name: Repair the dangling junction and forward setup options
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$installer = Join-Path $env:DEV_DRIVE "target/release/vp-setup.exe"
$stdout = Join-Path $env:RUNNER_TEMP "vp-setup.stdout.txt"
$stderr = Join-Path $env:RUNNER_TEMP "vp-setup.stderr.txt"
$pathBefore = [Environment]::GetEnvironmentVariable("Path", "User")
$process = Start-Process -FilePath $installer `
-ArgumentList @(
"--yes",
"--version", $env:VP_SETUP_TEST_VERSION,
"--registry", $env:VP_SETUP_TEST_REGISTRY,
"--no-node-manager",
"--no-modify-path"
) `
-RedirectStandardOutput $stdout -RedirectStandardError $stderr `
-Wait -PassThru -NoNewWindow
Get-Content -Raw $stdout | Write-Host
Get-Content -Raw $stderr | Write-Host
if ($process.ExitCode -ne 0) {
throw "vp-setup.exe exited with $($process.ExitCode)"
}
# NO_COLOR across the target CLI is deferred to a separate change.
if (-not (Test-Path (Join-Path $env:VP_HOME "current/bin/vp.exe"))) {
throw "vp-setup.exe did not repair the dangling current junction"
}
if (-not (Test-Path (Join-Path $env:VP_HOME "bin/vp.exe"))) {
throw "vp-setup.exe did not create the global vp.exe"
}
if (-not (Test-Path (Join-Path $env:VP_HOME "current/bin/.vp-setup-complete"))) {
throw "vp-setup.exe did not complete the binary's self-setup"
}
if ([Environment]::GetEnvironmentVariable("Path", "User") -cne $pathBefore) {
throw "--no-modify-path changed User PATH"
}
$config = Get-Content (Join-Path $env:VP_HOME "config.json") -Raw | ConvertFrom-Json
if ($config.nodeShimMode -ne "system_first") {
throw "--no-node-manager was not passed to self-setup"
}
- name: Unattended same-version installation works outside CI
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$installer = Join-Path $env:DEV_DRIVE "target/release/vp-setup.exe"
$previousCi = $env:CI
try {
# CI bypasses replacement confirmation and would hide lost --yes consent.
Remove-Item Env:CI -ErrorAction SilentlyContinue
foreach ($mode in @('--yes', '--quiet')) {
$previous = (Get-Item (Join-Path $env:VP_HOME "current")).Target
$output = & $installer $mode --version $env:VP_SETUP_TEST_VERSION `
--registry $env:VP_SETUP_TEST_REGISTRY --no-node-manager --no-modify-path 2>&1
if ($LASTEXITCODE -ne 0) {
throw "$mode setup failed: $output"
}
if ($mode -eq '--quiet' -and $output) {
throw "Quiet setup unexpectedly printed output: $output"
}
if (-not (Test-Path (Join-Path $env:VP_HOME "current/bin/.vp-setup-complete"))) {
throw "$mode setup did not finish"
}
if ((Get-Item (Join-Path $env:VP_HOME "current")).Target -eq $previous) {
throw "Same-version setup did not deploy a fresh binary"
}
}
} finally {
$env:CI = $previousCi
}
- name: Set PATH
shell: bash
run: echo "$USERPROFILE/.vite-plus/bin" >> $GITHUB_PATH
- name: Verify installation
shell: pwsh
run: |
vp --version
vp --help
- name: Verify installation (cmd)
shell: cmd
run: |
vp --version
vp --help
- name: Verify installation (bash)
shell: bash
run: |
vp --version
vp --help