From 48242e5bcd8cca361084a1fb4713f25fafcd07f6 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 14 Sep 2026 17:44:27 +0100 Subject: [PATCH 1/3] fix(installer): decode remote legacy PowerShell scripts --- .github/scripts/test-install-bootstrap.ps1 | 10 +++++++--- packages/cli/install.ps1 | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/scripts/test-install-bootstrap.ps1 b/.github/scripts/test-install-bootstrap.ps1 index 4f8f601eaf..9fc9ddec15 100644 --- a/.github/scripts/test-install-bootstrap.ps1 +++ b/.github/scripts/test-install-bootstrap.ps1 @@ -30,7 +30,7 @@ if ($args.Count -eq 0) { exit 0 } if ($env:VP_SELF_SETUP_SUPPORT_CHECK -ne '1') { exit 99 } -if ($scenario -in @('legacy', 'legacy-failure', 'pr')) { Write-Output 'Usage: vp [COMMAND]' } +if ($scenario -in @('legacy', 'legacy-remote', 'legacy-failure', 'pr')) { Write-Output 'Usage: vp [COMMAND]' } else { Write-Output 'vite-plus-self-setup-v1' } exit 0 '@ | Set-Content -LiteralPath "$testRoot/package/binary.ps1" @@ -60,6 +60,10 @@ function Invoke-WebRequest { if ($Method -eq 'Head') { return @{ Headers = @{ 'x-commit-key' = "voidzero-dev:vite-plus:$fixtureSha" } } } + if (-not $OutFile) { + $content = Get-Content -LiteralPath "$testRoot/scripts/install-legacy.ps1" -Raw + return @{ Content = [Text.Encoding]::UTF8.GetBytes($content) } + } Copy-Item -LiteralPath "$testRoot/payload.tgz" -Destination $OutFile } @@ -76,7 +80,7 @@ function Invoke-InstallHandoff { } try { - foreach ($scenario in @('supported', 'legacy', 'legacy-failure', 'failure', 'pr', 'supported-pr')) { + foreach ($scenario in @('supported', 'legacy', 'legacy-remote', 'legacy-failure', 'failure', 'pr', 'supported-pr')) { $env:Path = $originalPath $env:NPM_CONFIG_REGISTRY = 'https://custom.example' $script:Requests = New-Object 'System.Collections.Generic.List[string]' @@ -89,7 +93,7 @@ try { $ViteVersion = 'latest' $LocalTgz = $LocalBinary = $PrVersion = $PrCommitVersion = $null $NpmRegistry = 'https://custom.example' - $InstallerDirectory = "$testRoot/scripts" + $InstallerDirectory = if ($scenario -eq 'legacy-remote') { $null } else { "$testRoot/scripts" } if ($scenario -in @('pr', 'supported-pr')) { $PrVersion = '2406' } Main Assert ($env:NPM_CONFIG_REGISTRY -eq 'https://custom.example') 'Setup changed the caller registry' diff --git a/packages/cli/install.ps1 b/packages/cli/install.ps1 index 12cbbcb9a2..63c97ff4c0 100644 --- a/packages/cli/install.ps1 +++ b/packages/cli/install.ps1 @@ -337,7 +337,8 @@ function Invoke-LegacyInstaller { . $legacyScript -BinarySource $BinarySource -ResolvedVersion $ViteVersion -PreviewRef $PrVersion } else { $response = Invoke-WebRequest -Uri $LegacyInstallerUrl -UseBasicParsing - . ([scriptblock]::Create($response.Content)) -BinarySource $BinarySource -ResolvedVersion $ViteVersion -PreviewRef $PrVersion + $content = if ($response.Content -is [byte[]]) { [Text.Encoding]::UTF8.GetString($response.Content) } else { $response.Content } + . ([scriptblock]::Create($content)) -BinarySource $BinarySource -ResolvedVersion $ViteVersion -PreviewRef $PrVersion } # A child script's exit only returns to this bootstrap, so forward its failure. if ($LASTEXITCODE -ne 0) { From 736aa32da55a6bf7ef7d488807ac1122a2962246 Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 15 Sep 2026 01:24:35 +0800 Subject: [PATCH 2/3] fix(ci): align installer checks with native setup --- .github/workflows/test-standalone-install.yml | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test-standalone-install.yml b/.github/workflows/test-standalone-install.yml index 94f6ae7465..6955fb0978 100644 --- a/.github/workflows/test-standalone-install.yml +++ b/.github/workflows/test-standalone-install.yml @@ -283,7 +283,7 @@ jobs: exit 1 fi cat "$OUTPUT" - grep -F "Set VP_BIN_DIR, VP_DATA_DIR, and VP_CACHE_DIR together" "$OUTPUT" + grep -F "Set all three variables together: VP_BIN_DIR, VP_DATA_DIR, and VP_CACHE_DIR." "$OUTPUT" - name: Existing GitHub setup-vp uses legacy layout run: | @@ -556,10 +556,13 @@ jobs: vp env doctor test-install-sh-readonly-config: - name: Test install.sh (readonly shell config) + name: Test legacy install.sh (readonly shell config) runs-on: ubuntu-latest permissions: contents: read + env: + # These output assertions cover install-legacy.sh. Newer binaries run self-setup. + VP_VERSION: '0.2.9' steps: - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 @@ -1037,10 +1040,13 @@ jobs: vp --version test-install-ps1-release-age: - name: Test install.ps1 (minimum-release-age) + name: Test legacy install.ps1 (minimum-release-age) runs-on: namespace-profile-windows-4c-8g permissions: contents: read + env: + # The legacy script enforces release age; native self-setup uses a local override. + VP_VERSION: '0.2.9' steps: - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 @@ -1062,12 +1068,22 @@ jobs: 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" + # PowerShell 5.1 treats redirected native stderr as an error under Stop. + # Capture each stream in a file so the assertions can inspect the failure. + $stdout = Join-Path $env:RUNNER_TEMP "vp-release-age.stdout.txt" + $stderr = Join-Path $env:RUNNER_TEMP "vp-release-age.stderr.txt" + $installer = (Resolve-Path ./packages/cli/install.ps1).Path + $process = Start-Process -FilePath (Get-Command powershell).Source ` + -ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$installer`"") ` + -RedirectStandardOutput $stdout -RedirectStandardError $stderr ` + -Wait -PassThru + $text = @( + Get-Content -LiteralPath $stdout -Raw + Get-Content -LiteralPath $stderr -Raw + ) -join [Environment]::NewLine Write-Host $text - if ($exitCode -eq 0) { + if ($process.ExitCode -eq 0) { Write-Error "Expected install.ps1 to fail when pnpm minimum-release-age blocks vite-plus" exit 1 } @@ -1102,11 +1118,6 @@ jobs: 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) { @@ -1463,7 +1474,7 @@ jobs: 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")) { + if (-not $text.Contains("Set all three variables together: VP_BIN_DIR, VP_DATA_DIR, and VP_CACHE_DIR.")) { throw "install.ps1 did not report the incomplete override group" } From 42499dbac20c3bd9c702fa944433f37c7c55d905 Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 15 Sep 2026 01:36:42 +0800 Subject: [PATCH 3/3] test(installer): cover remote legacy PowerShell downloads --- .../scripts/test-install-legacy-remote.ps1 | 60 +++++++++++++++++++ .github/workflows/test-standalone-install.yml | 28 +++++++++ 2 files changed, 88 insertions(+) create mode 100644 .github/scripts/test-install-legacy-remote.ps1 diff --git a/.github/scripts/test-install-legacy-remote.ps1 b/.github/scripts/test-install-legacy-remote.ps1 new file mode 100644 index 0000000000..01cd7a945b --- /dev/null +++ b/.github/scripts/test-install-legacy-remote.ps1 @@ -0,0 +1,60 @@ +# Reproduce setup-vp's remote install of 0.2.6 in cloudflare/vinext#3251. +# Serve this checkout's scripts so the test does not depend on the deployed installer. +$ErrorActionPreference = 'Stop' +$root = Join-Path $env:RUNNER_TEMP 'vp-remote-legacy' +New-Item -ItemType Directory -Force -Path $root | Out-Null +Copy-Item (Join-Path $PSScriptRoot '../../packages/cli/install.ps1') "$root/install.txt" +Copy-Item (Join-Path $PSScriptRoot '../../packages/cli/install-legacy.ps1') "$root/install-legacy.bin" +$stdout = Join-Path $root 'server.stdout.txt' +$stderr = Join-Path $root 'server.stderr.txt' + +# Python serves .txt as text/plain and .bin as application/octet-stream. +# Port 0 selects an available port. Unbuffered output exposes it before any request. +$server = Start-Process -FilePath (Get-Command python).Source ` + -ArgumentList @('-u', '-m', 'http.server', '0', '--bind', '127.0.0.1', '--directory', "`"$root`"") ` + -RedirectStandardOutput $stdout -RedirectStandardError $stderr -PassThru +try { + $url = $null + $deadline = (Get-Date).AddSeconds(30) + while ((Get-Date) -lt $deadline) { + if ($server.HasExited) { + throw "Installer HTTP server exited: $(Get-Content -LiteralPath $stderr -Raw)" + } + $serverOutput = Get-Content -LiteralPath $stdout -Raw + if ($serverOutput -match 'Serving HTTP on 127\.0\.0\.1 port (\d+)') { + $url = "http://127.0.0.1:$($Matches[1])" + break + } + Start-Sleep -Milliseconds 100 + } + if (-not $url) { throw 'Installer HTTP server did not start' } + + $env:VP_HOME = Join-Path $root 'install' + $env:VP_TEST_INSTALLER_URL = "$url/install.txt" + $env:VP_LEGACY_INSTALLER_URL = "$url/install-legacy.bin" + $response = Invoke-WebRequest -Uri $env:VP_LEGACY_INSTALLER_URL -UseBasicParsing + if ($response.Content -isnot [byte[]]) { + throw "Expected a byte-array legacy response, got $($response.Content.GetType().FullName)" + } + + # setup-vp evaluates downloaded text in a script block, with no sibling legacy file. + # A child process also keeps installer exit calls from bypassing server cleanup. + $powershell = (Get-Process -Id $PID).Path + & $powershell -NoProfile -Command '& ([scriptblock]::Create((irm -TimeoutSec 15 $env:VP_TEST_INSTALLER_URL)))' + if ($LASTEXITCODE -ne 0) { throw "Remote install failed with exit code $LASTEXITCODE" } + + # One request above checks the response type; another must come from the installer. + $requests = @(Select-String -LiteralPath $stderr -Pattern 'GET /install-legacy.bin ' -SimpleMatch) + if ($requests.Count -ne 2) { throw 'The installer did not fetch the remote legacy script exactly once' } + + $vp = Join-Path $env:VP_HOME 'bin/vp.exe' + $version = & $vp --version + if ($LASTEXITCODE -ne 0 -or $version -notcontains "vp v$env:VP_VERSION") { + throw "Expected the installed vp to report version $env:VP_VERSION, got: $version" + } + Write-Host ($version -join [Environment]::NewLine) + & $vp env run --node 24 -- node --version + if ($LASTEXITCODE -ne 0) { throw 'The installed vp could not run Node.js 24' } +} finally { + Stop-Process -Id $server.Id -ErrorAction SilentlyContinue +} diff --git a/.github/workflows/test-standalone-install.yml b/.github/workflows/test-standalone-install.yml index 6955fb0978..bed6b99911 100644 --- a/.github/workflows/test-standalone-install.yml +++ b/.github/workflows/test-standalone-install.yml @@ -10,6 +10,7 @@ on: - 'packages/cli/install.ps1' - 'packages/cli/install-legacy.sh' - 'packages/cli/install-legacy.ps1' + - '.github/scripts/test-install-legacy-remote.ps1' - 'packages/tools/src/install-global-cli.ts' - 'packages/tools/src/local-npm-registry.ts' - 'crates/vp_installer/**' @@ -555,6 +556,33 @@ jobs: npm --version vp env doctor + test-install-ps1-remote-legacy: + name: Test remote legacy install.ps1 (${{ matrix.shell }}) + runs-on: windows-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + shell: [powershell, pwsh] + env: + # Reproduce the setup-vp install that failed in cloudflare/vinext#3251. + VP_VERSION: '0.2.6' + VP_NODE_MANAGER: 'yes' + GITHUB_ACTION_REPOSITORY: voidzero-dev/setup-vp + steps: + - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 + + - name: Install an old release through an HTTP byte-array response (PowerShell 5.1) + if: matrix.shell == 'powershell' + shell: powershell + run: ./.github/scripts/test-install-legacy-remote.ps1 + + - name: Install an old release through an HTTP byte-array response (PowerShell 7) + if: matrix.shell == 'pwsh' + shell: pwsh + run: ./.github/scripts/test-install-legacy-remote.ps1 + test-install-sh-readonly-config: name: Test legacy install.sh (readonly shell config) runs-on: ubuntu-latest