diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57e61487e9..fa0371781a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1106,6 +1106,9 @@ jobs: # trampoline would fail. current\bin\vp.exe is the real CLI. export VP_SNAP_GLOBAL_VP="$USERPROFILE/.vite-plus/current/bin/vp.exe" export VP_SNAP_JS_RUNTIME_DIR="$USERPROFILE/.vite-plus/js_runtime" + # Keep pwsh at its canonical installation path; copying pwsh.exe + # alone can break its adjacent runtime dependencies. + export VP_SNAP_PWSH_BIN="$(cygpath -w "$(command -v pwsh.exe)")" # --no-fail-fast: on a snapshot suite every diff is diagnostic # signal; cancelling on the first failure hides the rest. cargo-nextest nextest run --archive-file windows-snapshot-tests.tar.zst --workspace-remap . --no-fail-fast diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/README.md b/crates/vp_cli_snapshots/tests/cli_snapshots/README.md index 053f5fbcf8..90b2aafafe 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/README.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/README.md @@ -68,6 +68,7 @@ Environment overrides, mainly for CI: | `VP_SNAP_ZSH_BIN` | Zsh binary for cases that execute the generated `env` file | | `VP_SNAP_FISH_BIN` | Fish binary for cases that execute generated `env.fish` files | | `VP_SNAP_NU_BIN` | Nushell binary for cases that execute generated `env.nu` files | +| `VP_SNAP_PWSH_BIN` | PowerShell binary for cases that execute generated `env.ps1` files | | `VP_SNAP_SKIP_FLAVORS` | Comma-separated flavors to skip registering (e.g. `local`) | ## Case reference @@ -79,7 +80,7 @@ vp = "local" # "local" | "global" | ["local", "global"] comment = "What this proves." # rendered into the snapshot cwd = "packages/app" # optional, relative to the fixture root skip-platforms = ["windows"] # or { os = "linux", libc = "musl" } -requires = ["bash"] # "sh" | "bash" | "zsh" | "fish" | "nu" +requires = ["bash"] # "sh" | "bash" | "zsh" | "fish" | "nu" | "pwsh" ignore = false # true: only runs with `-- --ignored` seed-runtime = true # false: start from an empty VP_HOME link-node-modules = false # true: expose the run-root node_modules as @@ -122,9 +123,9 @@ A step is a bare argv array or a table: interactions = [ ... ] } ``` -`argv[0]` may be `vpt`, a runner-provisioned shell, or any -executable exposed by the case's Vite+ installation, including default shims -such as `vp`, `node`, and `corepack` and globally installed package binaries. +`argv[0]` may be `vpt`, a runner-approved shell, or any executable exposed by +the case's Vite+ installation, including default shims such as `vp`, `node`, +and `corepack` and globally installed package binaries. There is no shell: no `&&`, no redirects, no globs. File setup and assertions go through `vpt` so behavior is identical on every platform: @@ -189,10 +190,11 @@ case-owned tool dirs, then a system tail for child processes and direct `git` st `TERM=xterm-256color`, `VP_CLI_TEST=1`, `VP_EMIT_MILESTONES=1`, a fresh `HOME`, `VP_HOME`, and npm prefix. The runner still rejects direct step tools that resolve outside the case-owned dirs, except for `git`; `vpt` is the only -required runner helper on PATH, while optional shells are linked there when -available. `CI` and `NO_COLOR` are deliberately NOT -set: with a PTY attached, the CLI behaves interactively by default, which is -the point. +required runner helper on PATH, while optional POSIX, Fish, and Nushell binaries +are linked there when available. PowerShell runs from its canonical path so it +can load files installed beside `pwsh.exe`. `CI` and `NO_COLOR` are deliberately +NOT set: with a PTY attached, the CLI behaves interactively by default, which +is the point. `seed-runtime = true` (default) symlinks a provisioned managed Node runtime into the case `VP_HOME` so commands do not download ~50MB per case. diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_powershell/assert.ps1 b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_powershell/assert.ps1 new file mode 100644 index 0000000000..60b039fa4b --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_powershell/assert.ps1 @@ -0,0 +1,60 @@ +$ErrorActionPreference = "Stop" + +. (Join-Path $env:EXPECTED_VP_HOME "env.ps1") + +if ($env:VP_HOME -ne $env:EXPECTED_VP_HOME) { + throw "VP_HOME mismatch: expected $env:EXPECTED_VP_HOME, got $env:VP_HOME" +} + +$expectedBin = Join-Path $env:EXPECTED_VP_HOME "bin" +$binCount = @($env:Path -split [IO.Path]::PathSeparator | Where-Object { $_ -ieq $expectedBin }).Count +if ($binCount -ne 1) { + throw "PATH contains the Vite+ bin directory $binCount times" +} + +if (-not (Get-Command vp -CommandType Function -ErrorAction SilentlyContinue)) { + throw "env.ps1 did not define the vp wrapper" +} + +$vpOutput = vp --version +if ($LASTEXITCODE -ne 0) { + throw "vp --version failed through the PowerShell wrapper" +} +if ([string]::IsNullOrWhiteSpace(($vpOutput -join ""))) { + throw "vp --version returned no output" +} + +$env:VP_NODE_VERSION = "18.20.0" +vp env use --help *> $null +if ($LASTEXITCODE -ne 0) { + throw "vp env use --help failed through the PowerShell wrapper" +} +if ($env:VP_NODE_VERSION -ne "18.20.0") { + throw "vp env use --help changed VP_NODE_VERSION" +} + +vp env use 20.18.0 --no-install +if ($LASTEXITCODE -ne 0) { + throw "vp env use failed through the PowerShell wrapper" +} +if ($env:VP_NODE_VERSION -ne "20.18.0") { + throw "VP_NODE_VERSION mismatch: expected 20.18.0, got $env:VP_NODE_VERSION" +} + +vp env use --unset +if ($LASTEXITCODE -ne 0) { + throw "vp env use --unset failed through the PowerShell wrapper" +} +if (Test-Path Env:VP_NODE_VERSION) { + throw "vp env use --unset did not remove VP_NODE_VERSION" +} + +vp env use --no-install +if ($LASTEXITCODE -ne 0) { + throw "vp env use without a version failed through the PowerShell wrapper" +} +if ($env:VP_NODE_VERSION -ne "22.18.0") { + throw "file-based VP_NODE_VERSION mismatch: expected 22.18.0, got $env:VP_NODE_VERSION" +} + +Write-Output "PowerShell environment checks passed" diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_powershell/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_powershell/snapshots.toml new file mode 100644 index 0000000000..d04fa3e3a4 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_powershell/snapshots.toml @@ -0,0 +1,10 @@ +[[case]] +name = "command_env_powershell" +vp = "global" +skip-platforms = ["linux", "macos"] +requires = ["pwsh"] +steps = [ + { argv = ["vp", "env", "setup", "--refresh"], snapshot = false }, + { argv = ["vpt", "write-file", ".node-version", "22.18.0\n"], snapshot = false }, + { argv = ["pwsh", "-NoLogo", "-NoProfile", "-NonInteractive", "-File", "assert.ps1"], comment = "dot-sources env.ps1 and verifies PowerShell environment setup and wrapper behavior", envs = [["EXPECTED_VP_HOME", "${VP_HOME}"]] }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_powershell/snapshots/command_env_powershell.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_powershell/snapshots/command_env_powershell.md new file mode 100644 index 0000000000..a211b81d65 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_env_powershell/snapshots/command_env_powershell.md @@ -0,0 +1,19 @@ +# command_env_powershell + +## `vp env setup --refresh` + + +## `vpt write-file .node-version '22.18.0 +'` + + +## `EXPECTED_VP_HOME=${VP_HOME} pwsh -NoLogo -NoProfile -NonInteractive -File assert.ps1` + +dot-sources env.ps1 and verifies PowerShell environment setup and wrapper behavior + +``` +Using Node.js (resolved from 20.18.0) +Reverted to file-based Node.js version resolution +Using Node.js (resolved from .node-version) +PowerShell environment checks passed +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/flavor.rs b/crates/vp_cli_snapshots/tests/cli_snapshots/flavor.rs index 43f04eab6b..1e7293fd90 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/flavor.rs +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/flavor.rs @@ -7,6 +7,8 @@ //! Each flavor gets one runner bin directory per run (created under the run //! temp root) for runner-owned helpers. `vpt` always lives there; optional //! external shells are linked there when available. +//! PowerShell keeps its original installation path because `pwsh.exe` may +//! depend on files installed beside it. use std::path::{Path, PathBuf}; @@ -41,6 +43,9 @@ pub struct FlavorRuntime { /// Runner-owned Nushell binary used by fixtures that execute generated /// `env.nu` files. CI supplies it through `VP_SNAP_NU_BIN`. pub nu: Option, + /// Canonical PowerShell binary used by fixtures that execute generated + /// `env.ps1` files. CI supplies it through `VP_SNAP_PWSH_BIN`. + pub pwsh: Option, /// Source global `vp` binary to install into each case's `VP_HOME/current`. pub global_vp: PathBuf, /// Source package installed into each case's `VP_HOME/current/node_modules`. @@ -241,6 +246,12 @@ pub fn nushell_path() -> Result, String> { optional_tool_path("VP_SNAP_NU_BIN", "nu") } +/// Resolves an optional PowerShell binary for fixtures that exercise generated +/// `env.ps1` files. +pub fn powershell_path() -> Result, String> { + optional_tool_path("VP_SNAP_PWSH_BIN", "pwsh") +} + /// Home-layout names, shared with `CaseHome` in main.rs so the product's /// `~/.vite-plus/js_runtime` layout is spelled once. pub const VP_HOME_DIR: &str = ".vite-plus"; @@ -349,10 +360,24 @@ pub fn provision(flavor: Flavor, run_root: &Path) -> Result local_cli_package_dir()?, Flavor::Global => repo_root().join("packages/cli"), }; - Ok(FlavorRuntime { runner_bin_dir, vpt, sh, bash, zsh, fish, nu, global_vp, cli_package_dir }) + Ok(FlavorRuntime { + runner_bin_dir, + vpt, + sh, + bash, + zsh, + fish, + nu, + pwsh, + global_vp, + cli_package_dir, + }) } diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/main.rs b/crates/vp_cli_snapshots/tests/cli_snapshots/main.rs index 24f7de7e62..5d037dd572 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/main.rs +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/main.rs @@ -324,6 +324,7 @@ enum RequiredTool { Zsh, Fish, Nu, + Pwsh, } impl RequiredTool { @@ -336,6 +337,7 @@ impl RequiredTool { Self::Zsh => matches!(flavor::zsh_path(), Ok(None)), Self::Fish => matches!(flavor::fish_path(), Ok(None)), Self::Nu => matches!(flavor::nushell_path(), Ok(None)), + Self::Pwsh => matches!(flavor::powershell_path(), Ok(None)), } } } @@ -470,6 +472,7 @@ struct CaseInstall { zsh: Option, fish: Option, nu: Option, + pwsh: Option, } impl CaseInstall { @@ -514,6 +517,13 @@ impl CaseInstall { .to_owned() }); } + if program == "pwsh" { + return self.pwsh.clone().ok_or_else(|| { + "`pwsh` is required by this snapshot case; install PowerShell or set \ + VP_SNAP_PWSH_BIN" + .to_owned() + }); + } // An explicit `./`-prefixed program runs a file the case itself // produced inside the staged workspace (a packed executable); the @@ -614,6 +624,7 @@ impl CaseHome { zsh: runtime.zsh.clone(), fish: runtime.fish.clone(), nu: runtime.nu.clone(), + pwsh: runtime.pwsh.clone(), }) }