Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4fe5a90
refactor(installer): separate bootstrap from legacy setup
liangmiQwQ Sep 5, 2026
561b56a
feat(cli): set up unmarked binaries on first launch
liangmiQwQ Sep 5, 2026
8bb2130
refactor(installer): delegate installation to binary self-setup
liangmiQwQ Sep 5, 2026
64f7a24
fix(installer): preserve bootstrap compatibility and failure status
liangmiQwQ Sep 5, 2026
33309ef
test(installer): preserve setup state in upgrade fixtures
liangmiQwQ Sep 5, 2026
818deea
fix(cli): honor NO_COLOR in shared output prefixes
liangmiQwQ Sep 5, 2026
510120e
fix(installer): propagate shell failures and defer color cleanup
liangmiQwQ Sep 5, 2026
072785d
fix(installer): return resolved directories to the calling shell
liangmiQwQ Sep 5, 2026
95c99ba
fix(installer): preserve interactive shell handoff behavior
liangmiQwQ Sep 5, 2026
013777b
fix(installer): use bridge registry for preview self-setup
liangmiQwQ Sep 5, 2026
4179d59
fix(installer): retain terminal for interactive self-setup
liangmiQwQ Sep 5, 2026
8dccf47
fix(installer): confirm entrypoint replacement and tolerate profile e…
liangmiQwQ Sep 5, 2026
d001646
fix(cli): use colorful theme for self-setup confirmations
liangmiQwQ Sep 5, 2026
be0e698
Merge branch 'main' into liang/codex/refactor-installer
liangmiQwQ Sep 6, 2026
21642b7
Merge branch 'main' into liang/codex/refactor-installer
liangmiQwQ Sep 6, 2026
73d6136
chore(ci): retry flaky Windows Socket Firewall test
liangmiQwQ Sep 6, 2026
1d1849a
chore(ci): trigger fresh CI validation
liangmiQwQ Sep 6, 2026
2ff63a9
Merge branch 'main' into liang/codex/refactor-installer
liangmiQwQ Sep 6, 2026
dab8ba0
fix(ci): skip self-setup for packaged planner verification
liangmiQwQ Sep 6, 2026
0d2cb88
fix(ci): verify certless commands bypass self-setup
liangmiQwQ Sep 6, 2026
8f1ee79
Merge branch 'main' into liang/codex/refactor-installer
liangmiQwQ Sep 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions .github/scripts/test-install-bootstrap.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Run locally on Windows with ./.github/scripts/test-install-bootstrap.ps1; no registry or installed vp needed.
$ErrorActionPreference = 'Stop'
$source = Get-Content -LiteralPath (Join-Path $PSScriptRoot '../../packages/cli/install.ps1') -Raw
. ([scriptblock]::Create(($source -replace '(?m)^ Main\r?$', '')))
function Exit-Installer { param([int]$Code = 1); $script:ExitCode = $Code; throw $script:InstallStopSignal }
function Assert($Condition, [string]$Message) {
if (-not $Condition) { throw $Message }
}

$testRoot = Join-Path $env:TEMP "vite-bootstrap-test-$(Get-Random)"
$originalTemp = $env:TEMP
$originalCheck = $env:VP_SELF_SETUP_SUPPORT_CHECK
$originalPath = $env:Path
$originalRegistry = $env:NPM_CONFIG_REGISTRY
$fixtureSha = '0123456789012345678901234567890123456789'
New-Item -ItemType Directory -Path "$testRoot/package", "$testRoot/tmp", "$testRoot/scripts" | Out-Null
Set-Content -LiteralPath "$testRoot/package/vp.exe" -Value 'Payload fixture'
@'
if ($args.Count -eq 0) {
if (Test-Path Env:VP_SELF_SETUP_SUPPORT_CHECK) { exit 99 }
New-Item -ItemType File -Path "$testRoot/binary-invoked" | Out-Null
if ($scenario -eq 'failure') { exit 42 }
if ($env:VP_SELF_SETUP_SHELL -ne 'powershell') { exit 98 }
if ($scenario -eq 'supported-pr' -and $env:NPM_CONFIG_REGISTRY -ne 'https://registry-bridge.viteplus.dev/') { exit 97 }
Write-Output ("`$script:InstallDir = '{0}'" -f "$testRoot/data")
Write-Output ("`$script:ShimDir = '{0}'" -f "$testRoot/installed bin")
Write-Output ("`$script:CacheDir = '{0}'" -f "$testRoot/cache")
Write-Output ("`$script:ConfigDir = '{0}'" -f "$testRoot/config")
Write-Output ("`$script:StateDir = '{0}'" -f "$testRoot/state")
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]' }
else { Write-Output 'vite-plus-self-setup-v1' }
exit 0
'@ | Set-Content -LiteralPath "$testRoot/package/binary.ps1"
@'
param($BinarySource, $ResolvedVersion, $PreviewRef)
$script:InstallDir = "$testRoot/data"
$script:ShimDir = "$testRoot/installed bin"
$script:CacheDir = "$testRoot/cache"
$script:ConfigDir = "$testRoot/config"
$script:StateDir = "$testRoot/state"
if (-not [System.IO.Path]::IsPathRooted($BinarySource) -or -not (Test-Path -LiteralPath $BinarySource)) { throw 'Invalid payload path' }
@($ResolvedVersion, $PreviewRef) | Set-Content -LiteralPath "$testRoot/legacy"
if ($scenario -eq 'legacy-failure') { exit 42 }
'@ | Set-Content -LiteralPath "$testRoot/scripts/install-legacy.ps1"
& "$env:SystemRoot\System32\tar.exe" -czf "$testRoot/payload.tgz" -C $testRoot package
Assert ($LASTEXITCODE -eq 0) 'Could not create fixture'
$env:TEMP = "$testRoot/tmp"

function Invoke-RestMethod {
param($Uri)
$script:Requests.Add("GET $Uri")
return @{ version = '0.2.9' }
}
function Invoke-WebRequest {
param($Uri, $Method, $OutFile, [switch]$UseBasicParsing, $ErrorAction)
$script:Requests.Add("$Method $Uri")
if ($Method -eq 'Head') {
return @{ Headers = @{ 'x-commit-key' = "voidzero-dev:vite-plus:$fixtureSha" } }
}
Copy-Item -LiteralPath "$testRoot/payload.tgz" -Destination $OutFile
}

# Use an executable script fixture so these checks need no native compiler.
$probe = ${function:Test-SelfSetupSupport}
function Test-SelfSetupSupport {
param($BinarySource)
& $probe -BinarySource (Join-Path (Split-Path $BinarySource) 'binary.ps1')
}
$handoff = ${function:Invoke-InstallHandoff}
function Invoke-InstallHandoff {
param($BinarySource)
& $handoff -BinarySource (Join-Path (Split-Path $BinarySource) 'binary.ps1')
}

try {
foreach ($scenario in @('supported', 'legacy', '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]'
$script:ExitCode = 0
$script:PackageMetadata = $null
Remove-Item -LiteralPath "$testRoot/legacy", "$testRoot/binary-invoked" -ErrorAction SilentlyContinue
$env:VP_SELF_SETUP_SUPPORT_CHECK = if ($scenario -eq 'supported') { 'original' } else { $null }
try {
& {
$ViteVersion = 'latest'
$LocalTgz = $LocalBinary = $PrVersion = $PrCommitVersion = $null
$NpmRegistry = 'https://custom.example'
$InstallerDirectory = "$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'
Assert ($script:InstallDir -eq "$testRoot/data") 'InstallDir was lost'
Assert ($script:ShimDir -eq "$testRoot/installed bin") 'ShimDir was lost'
Assert ($script:CacheDir -eq "$testRoot/cache") 'CacheDir was lost'
Assert ($script:ConfigDir -eq "$testRoot/config") 'ConfigDir was lost'
Assert ($script:StateDir -eq "$testRoot/state") 'StateDir was lost'
}
} catch {
if ($scenario -notin @('failure', 'legacy-failure') -or -not (Test-IsInstallStopException $_)) { throw }
}
$expectedExit = if ($scenario -in @('failure', 'legacy-failure')) { 42 } else { 0 }
Assert ($script:ExitCode -eq $expectedExit) 'Binary exit code was lost'
if ($scenario -eq 'supported') {
Assert (($env:Path -split ';')[0] -eq "$testRoot/installed bin") 'Installed bin directory was not added to the current PATH'
} elseif ($scenario -eq 'failure') {
Assert ($env:Path -eq $originalPath) 'Failed setup changed the current PATH'
}
$usesBinary = $scenario -in @('supported', 'supported-pr', 'failure')
Assert ((Test-Path -LiteralPath "$testRoot/binary-invoked") -eq $usesBinary) 'Incorrect binary invocation'
Assert ((Test-Path -LiteralPath "$testRoot/legacy") -eq (-not $usesBinary)) 'Incorrect legacy invocation'
if ($scenario -eq 'pr') {
$record = @(Get-Content -LiteralPath "$testRoot/legacy")
Assert ($record[0] -eq "0.0.0-commit.$fixtureSha" -and $record[1] -eq '2406') 'Resolved preview identity was lost'
Assert ($script:Requests[-1].EndsWith("@$fixtureSha")) 'Payload used a mutable ref'
Assert ($script:Requests.Count -eq 2) 'Preview was resolved or downloaded more than once'
}
Assert (@(Get-ChildItem -LiteralPath "$testRoot/tmp" -Force).Count -eq 0) 'Temporary payload was not cleaned up'
Write-Host "PASS: $scenario"
}
} finally {
$env:VP_SELF_SETUP_SUPPORT_CHECK = $originalCheck
$env:Path = $originalPath
$env:NPM_CONFIG_REGISTRY = $originalRegistry
$env:TEMP = $originalTemp
Remove-Item -LiteralPath $testRoot -Recurse -Force
$global:LASTEXITCODE = 0
}
118 changes: 118 additions & 0 deletions .github/scripts/test-install-bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/bin/bash
# Run locally with bash .github/scripts/test-install-bootstrap.sh; no registry or installed vp needed.
set -eu
cd "$(dirname "$0")/../.."
eval "$(sed '/^main "[$]@"$/d' packages/cli/install.sh)"

test_root=$(mktemp -d)
trap 'rm -rf "$test_root"' EXIT
export test_root
mkdir -p "$test_root/package" "$test_root/tmp" "$test_root/scripts"
touch "$test_root/scripts/install.sh"
cat > "$test_root/scripts/install-legacy.sh" <<'LEGACY'
#!/bin/bash
set -eu
INSTALL_DIR="$test_root/data"
SHIM_DIR="$test_root/installed bin"
CACHE_DIR="$test_root/cache"
CONFIG_DIR="$test_root/config"
STATE_DIR="$test_root/state"
case "$1" in /*) ;; *) exit 80 ;; esac
test -f "$1"
printf '%s\n' "$2" "$3" > "$test_root/legacy"
case "$scenario" in
legacy-failure|piped-legacy-failure) exit 43 ;;
esac
LEGACY
cat > "$test_root/package/vp" <<'BINARY'
#!/bin/bash
if [ "$#" -eq 0 ]; then
test -z "${VP_SELF_SETUP_SUPPORT_CHECK+x}" || exit 99
touch "$test_root/binary-invoked"
if [ "$scenario" = failure ]; then exit 42; fi
test "${VP_SELF_SETUP_SHELL:-}" = sh || exit 98
if [ "$scenario" = supported-pr ]; then
test "$NPM_CONFIG_REGISTRY" = https://registry-bridge.viteplus.dev/ || exit 97
fi
printf 'INSTALL_DIR=%q\n' "$test_root/data"
printf 'SHIM_DIR=%q\n' "$test_root/installed bin"
printf 'CACHE_DIR=%q\n' "$test_root/cache"
printf 'CONFIG_DIR=%q\n' "$test_root/config"
printf 'STATE_DIR=%q\n' "$test_root/state"
exit 0
fi
test "${VP_SELF_SETUP_SUPPORT_CHECK:-}" = 1 || exit 99
case "$scenario" in
legacy|legacy-failure|piped-legacy|piped-legacy-failure|pr) printf 'Usage: vp [COMMAND]\n' ;;
*) printf 'vite-plus-self-setup-v1\n' ;;
esac
BINARY
chmod +x "$test_root/package/vp"
tar czf "$test_root/payload.tgz" -C "$test_root" package
export TMPDIR="$test_root/tmp"
fixture_sha=0123456789012345678901234567890123456789

# Only transport is substituted; extraction, probing, and dispatch run normally.
curl() {
printf '%s\n' "$*" >> "$test_root/requests"
case "$*" in
*file://*) command curl "$@" ;;
*-fsSIL*) printf 'x-commit-key: voidzero-dev:vite-plus:%s\r\n' "$fixture_sha" ;;
*'https://custom.example/vite-plus/'*) printf '{"version":"0.2.9"}\n' ;;
*) cp "$test_root/payload.tgz" "${@: -1}" ;;
esac
}

for scenario in supported legacy legacy-failure piped-legacy piped-legacy-failure failure pr supported-pr; do
export scenario
: > "$test_root/requests"
rm -f "$test_root/legacy" "$test_root/binary-invoked"
set +e
(
set -e
VP_VERSION=latest
LOCAL_TGZ="" LOCAL_BINARY="" PR_VERSION="" PACKAGE_METADATA=""
NPM_REGISTRY=https://custom.example
export NPM_CONFIG_REGISTRY="$NPM_REGISTRY"
INSTALLER_PATH="$test_root/scripts/install.sh"
if [[ "$scenario" == piped-legacy* ]]; then
INSTALLER_PATH=""
LEGACY_INSTALLER_URL="file://$test_root/scripts/install-legacy.sh"
fi
if [[ "$scenario" == *pr ]]; then PR_VERSION=2406; fi
if [ "$scenario" = supported ]; then export VP_SELF_SETUP_SUPPORT_CHECK=original; fi
main
test "$NPM_CONFIG_REGISTRY" = https://custom.example
test "$INSTALL_DIR" = "$test_root/data"
test "$SHIM_DIR" = "$test_root/installed bin"
test "$CACHE_DIR" = "$test_root/cache"
test "$CONFIG_DIR" = "$test_root/config"
test "$STATE_DIR" = "$test_root/state"
) > "$test_root/output" 2>&1
status=$?
set -e
if [ "$scenario" = failure ]; then
test "$status" -eq 42
elif [[ "$scenario" == *legacy-failure ]]; then
test "$status" -eq 43
elif [ "$status" -ne 0 ]; then
cat "$test_root/output"
exit 1
fi
case "$scenario" in
supported|supported-pr|failure)
test -f "$test_root/binary-invoked"
test ! -f "$test_root/legacy" ;;
legacy|legacy-failure|piped-legacy|piped-legacy-failure|pr)
test -f "$test_root/legacy"
test ! -f "$test_root/binary-invoked" ;;
esac
if [ "$scenario" = pr ]; then
test "$(head -1 "$test_root/legacy")" = "0.0.0-commit.$fixture_sha"
test "$(tail -1 "$test_root/legacy")" = 2406
grep -q "@$fixture_sha -o" "$test_root/requests"
test "$(wc -l < "$test_root/requests" | tr -d ' ')" = 2
fi
test -z "$(ls -A "$test_root/tmp")"
echo "PASS: $scenario"
done
9 changes: 8 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,8 @@ jobs:
$initialBinDir = Join-Path $initialVersionDir "bin"
New-Item -ItemType Directory -Path $initialBinDir | Out-Null
Copy-Item $currentVp (Join-Path $initialBinDir "vp.exe")
# Preserve installed state so the first invocation executes upgrade.
Copy-Item (Join-Path (Split-Path $currentVp) ".vp-setup-complete") $initialBinDir
New-Item -ItemType Junction -Path (Join-Path $tempVpHome "current") -Target $initialVersionDir | Out-Null

$env:VP_HOME = $tempVpHome
Expand Down Expand Up @@ -815,6 +817,8 @@ jobs:
initial_bin_dir="$temp_vp_home/$version/bin"
mkdir -p "$initial_bin_dir"
cp "$current_vp" "$initial_bin_dir/vp"
# Preserve installed state so the first invocation executes upgrade.
cp "$(dirname "$current_vp")/.vp-setup-complete" "$initial_bin_dir/"
chmod +x "$initial_bin_dir/vp"
# Relative symlink, matching swap_current_link's `current -> <version>`.
ln -s "$version" "$temp_vp_home/current"
Expand Down Expand Up @@ -1255,10 +1259,13 @@ jobs:
# The image must actually lack the CA bundle, or this job stops
# guarding anything.
test ! -e /etc/ssl/certs/ca-certificates.crt
# Exercise commands on the mounted build without installing a published package.
touch /usr/local/bin/.vp-setup-complete
vp --version
# HTTPS to nodejs.org through the shared client; exits nonzero
# without the bundled-roots fallback.
vp env list-remote --lts
vp env list-remote --lts --json > /tmp/vp-versions.json
node -e "const versions = require(\"/tmp/vp-versions.json\"); if (!versions.node?.length) process.exit(1)"
'

install-e2e-test:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/deploy-docs-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ on:
- 'docs/**'
- 'packages/cli/install.sh'
- 'packages/cli/install.ps1'
- 'packages/cli/install-legacy.sh'
- 'packages/cli/install-legacy.ps1'
- '.github/workflows/deploy-docs-main.yml'
- '.github/actions/deploy-docs/**'

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/deploy-docs-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ on:
- 'docs/**'
- 'packages/cli/install.sh'
- 'packages/cli/install.ps1'
- 'packages/cli/install-legacy.sh'
- 'packages/cli/install-legacy.ps1'
- '.github/workflows/deploy-docs-preview.yml'
- '.github/actions/deploy-docs/**'

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish-preview-register.yml
Original file line number Diff line number Diff line change
Expand Up @@ -388,11 +388,11 @@ jobs:
'',
'```bash',
'# macOS / Linux',
`curl -fsSL ${installerScripts}/install.sh | VP_PR_VERSION=${pr} bash`,
`curl -fsSL ${installerScripts}/install.sh | VP_PR_VERSION=${pr} VP_LEGACY_INSTALLER_URL=${installerScripts}/install-legacy.sh bash`,
'```',
'```powershell',
'# Windows (PowerShell)',
`$env:VP_PR_VERSION="${pr}"; irm ${installerScripts}/install.ps1 | iex`,
`$env:VP_PR_VERSION="${pr}"; $env:VP_LEGACY_INSTALLER_URL="${installerScripts}/install-legacy.ps1"; irm ${installerScripts}/install.ps1 | iex`,
'```',
'',
'**Or download the standalone Windows installer built from this commit:**',
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/reusable-release-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ jobs:
}
trap cleanup EXIT
tar -xzf "vp-${{ matrix.settings.target }}.tar.gz" -C "${archive_dir}"
# Skip first-start setup because this build's preview packages are not published yet.
touch "${archive_dir}/.vp-setup-complete"

node --input-type=module -e '
const before = JSON.stringify({ devDependencies: { "vite-plus": "0.0.0" } }) + "\n";
Expand Down
Loading
Loading