feat(test): upgrade to Vitest v5 with migration support #1523
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |