Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,13 @@ echo "8.3" > .phpvmrc

Then run `phpvm auto` from anywhere in the project — phpvm walks up to find the nearest `.phpvmrc` and prepends that version to your shell `PATH` (session only, your global `phpvm use` is untouched).

For hands-off switching, install the PowerShell prompt hook:
For hands-off switching, enable the PowerShell prompt hook:

```powershell
phpvm hook install # adds a snippet to $PROFILE
phpvm hook enable # adds a snippet to $PROFILE
# restart PowerShell, then `cd` between projects - phpvm auto-switches per directory
phpvm hook status # check whether the hook is installed
phpvm hook uninstall # remove the hook
phpvm hook status # check whether the hook is enabled
phpvm hook disable # remove the hook
```

`.phpvmrc` accepts a full semver (`8.3.0`), a major.minor (`8.3` — picks the highest installed patch), or a leading `v` (`v8.3.0`). Lines starting with `#` are comments. If the version is not installed locally, phpvm warns but never auto-installs.
Expand Down Expand Up @@ -203,6 +203,14 @@ phpvm composer # installs a single global composer that follows

The installer signature is verified against `composer.github.io/installer.sig` (SHA-384) before execution. Composer is installed **once** — `composer.phar` in `~/.phpvm/` and a shim in `~/.phpvm/bin/` (on PATH) that runs whatever PHP is active. Switch versions with `phpvm use <other>` and the same `composer` keeps working; no need to re-run `phpvm composer`. (Composer 2.x requires PHP ≥ 7.2.5, so an extremely old active version won't run the latest composer.)

### WP-CLI

```bash
phpvm wp-cli # installs a single global `wp` command that follows the active PHP version
```

Same global-install model as Composer: `wp-cli.phar` lands in `~/.phpvm/` and a `wp` shim in `~/.phpvm/bin/` (on PATH) runs whatever PHP is active — switch with `phpvm use <other>` and `wp` keeps working. The phar is verified against the upstream `wp-cli.phar.sha512` checksum (SHA-512 — WP-CLI publishes SHA-512, unlike Composer's SHA-384 installer signature). WP-CLI itself requires PHP ≥ 7.2.24 and reports clearly if the active version is older.

### Fix `php.ini` extension_dir

```bash
Expand Down Expand Up @@ -343,7 +351,7 @@ your distro, and check the tail of `~/.phpvm/build.log` for the first error.

### `.phpvmrc` doesn't auto-switch

The shell hook isn't active. Windows: `phpvm hook install`, then open a new
The shell hook isn't active. Windows: `phpvm hook enable`, then open a new
terminal. Linux: make sure your `~/.bashrc` / `~/.zshrc` sources
`~/.phpvm/phpvm.sh`. Note that auto-switch never installs missing versions —
it only switches between installed ones.
Expand Down
2 changes: 1 addition & 1 deletion linux/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

set -e

PHPVM_VERSION="1.10.0"
PHPVM_VERSION="1.11.0"
PHPVM_DIR="${PHPVM_DIR:-$HOME/.phpvm}"
PHPVM_REPO="https://raw.githubusercontent.com/devhardiyanto/phpvm/main"

Expand Down
79 changes: 74 additions & 5 deletions linux/phpvm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# phpvm use 8.3.0
# ==============================================================================

PHPVM_VERSION="1.10.0"
PHPVM_VERSION="1.11.0"
PHPVM_DIR="${PHPVM_DIR:-$HOME/.phpvm}"
PHPVM_VERSIONS="$PHPVM_DIR/versions"
PHPVM_CURRENT="$PHPVM_DIR/current"
Expand Down Expand Up @@ -1094,6 +1094,73 @@ EOF
_dim "Composer follows your active PHP version — no need to re-run after 'phpvm use'."
}

# ==============================================================================
# WP-CLI (one global wp that follows the active PHP version)
# ==============================================================================
phpvm_wp_cli() {
local cur
cur=$(_phpvm_current_version)
[[ -z "$cur" ]] && { _err "No active PHP version. Run: phpvm use <version>"; return 1; }

local php_bin="$PHPVM_VERSIONS/$cur/bin/php"
[[ ! -x "$php_bin" ]] && { _err "php binary not found: $php_bin"; return 1; }

local phar="$PHPVM_DIR/wp-cli.phar"
local shim="$PHPVM_BIN/wp"

if [[ -x "$shim" && -f "$phar" ]]; then
_warn "WP-CLI already installed at $shim"
_dim "It follows your active PHP version automatically."
_dim "Run: wp --version"
return 0
fi

local phar_url="https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar"
local hash_url="$phar_url.sha512"

_step "Downloading WP-CLI ..."
if command -v curl &>/dev/null; then
curl -fsSL "$phar_url" -o "$phar" || { _err "Download failed."; rm -f "$phar"; return 1; }
elif command -v wget &>/dev/null; then
wget -qO "$phar" "$phar_url" || { _err "Download failed."; rm -f "$phar"; return 1; }
else
_err "curl or wget is required."; return 1
fi

_step "Verifying SHA-512 ..."
local expected actual
if command -v curl &>/dev/null; then
expected=$(curl -fsSL "$hash_url" 2>/dev/null | awk '{print $1}')
else
expected=$(wget -qO- "$hash_url" 2>/dev/null | awk '{print $1}')
fi
# Hash through PHP itself (always present) — no sha512sum/shasum coreutils
# split between GNU and BSD/macOS.
actual=$("$php_bin" -r "echo hash_file('sha512', '$phar');" 2>/dev/null)

if [[ -z "$expected" || "$actual" != "$expected" ]]; then
_err "SHA-512 mismatch! Phar may be corrupt or tampered."
rm -f "$phar"
return 1
fi
_ok "SHA-512 verified."

mkdir -p "$PHPVM_BIN"
cat > "$shim" <<EOF
#!/usr/bin/env sh
exec "$PHPVM_CURRENT/bin/php" "$phar" "\$@"
EOF
chmod +x "$shim"

_ok "WP-CLI installed (global)!"
_ok " phar : $phar"
_ok " shim : $shim"
echo ""
"$php_bin" "$phar" --version 2>/dev/null | sed 's/^/ /'
echo ""
_dim "WP-CLI follows your active PHP version — no need to re-run after 'phpvm use'."
}

# ==============================================================================
# FIX-INI (sync extension_dir in active php.ini)
# ==============================================================================
Expand Down Expand Up @@ -1177,8 +1244,9 @@ phpvm_help() {
phpvm fix-ini Sync extension_dir in active php.ini
phpvm deps Print dependency install command

COMPOSER
COMPOSER / WP-CLI
phpvm composer Install Composer for active PHP version
phpvm wp-cli Install WP-CLI (global 'wp' command)

AUTO-SWITCH (.phpvmrc)
phpvm auto Switch to the version named in .phpvmrc
Expand Down Expand Up @@ -1290,13 +1358,13 @@ phpvm_hook() {
local sub="${1:-status}"
local flag="$PHPVM_DIR/.auto-hook"
case "$sub" in
enable|install)
enable)
mkdir -p "$PHPVM_DIR"
touch "$flag"
_ok "Hook enabled. Restart your shell, or run:"
_dim " source \"$PHPVM_DIR/phpvm.sh\""
;;
disable|uninstall|remove)
disable)
if [[ -f "$flag" ]]; then
rm -f "$flag"
_ok "Hook disabled. Restart your shell to fully unregister."
Expand Down Expand Up @@ -1352,7 +1420,7 @@ _phpvm_levenshtein() {
}

# Canonical command list (includes aliases) for suggestions.
_PHPVM_COMMANDS="install use list ls current uninstall remove which ini deps ext composer fix-ini auto hook upgrade update version help"
_PHPVM_COMMANDS="install use list ls current uninstall remove which ini deps ext composer wp-cli fix-ini auto hook upgrade update version help"

# Unknown command: suggest the nearest match instead of dumping the full help.
_phpvm_unknown() {
Expand Down Expand Up @@ -1389,6 +1457,7 @@ phpvm() {
deps) phpvm_deps ;;
ext) phpvm_ext "$@" ;;
composer) phpvm_composer ;;
wp-cli) phpvm_wp_cli ;;
fix-ini) phpvm_fix_ini ;;
auto) phpvm_auto ;;
hook) phpvm_hook "$@" ;;
Expand Down
72 changes: 72 additions & 0 deletions tests/linux/commands.bats
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,71 @@ EOF
[[ "$output" == *"already installed"* ]]
}

# ---------- phpvm_wp_cli ----------

# Shell-function stub for curl: serves a fake phar for the download and
# $FAKE_WP_SHA for the .sha512 URL. `command -v curl` resolves functions too.
_stub_curl() {
curl() {
if [[ "$*" == *".sha512"* ]]; then
printf '%s wp-cli.phar\n' "$FAKE_WP_SHA"
return 0
fi
local out="" prev=""
for a in "$@"; do
[[ "$prev" == "-o" ]] && out="$a"
prev="$a"
done
[[ -n "$out" ]] && echo "fake phar" > "$out"
return 0
}
}

@test "wp-cli: errors when no active version" {
eval "_phpvm_current_version() { echo ''; }"
run phpvm_wp_cli
[ "$status" -ne 0 ]
[[ "$output" == *"No active PHP version"* ]]
}

@test "wp-cli: no-op when global shim + phar already present" {
_fake_php_install 8.3.0
mkdir -p "$PHPVM_BIN"
touch "$PHPVM_DIR/wp-cli.phar"
cat > "$PHPVM_BIN/wp" <<'EOF'
#!/usr/bin/env sh
EOF
chmod +x "$PHPVM_BIN/wp"
run phpvm_wp_cli
[ "$status" -eq 0 ]
[[ "$output" == *"already installed"* ]]
}

@test "wp-cli: downloads phar, verifies hash, writes wp shim" {
_fake_php_install 8.3.0
_stub_curl
export FAKE_WP_SHA="cafe123"
export FAKE_PHP_HASH="cafe123"
run phpvm_wp_cli
[ "$status" -eq 0 ]
[[ "$output" == *"WP-CLI installed"* ]]
[ -f "$PHPVM_DIR/wp-cli.phar" ]
[ -x "$PHPVM_BIN/wp" ]
grep -q "wp-cli.phar" "$PHPVM_BIN/wp"
}

@test "wp-cli: removes phar and writes no shim on hash mismatch" {
_fake_php_install 8.3.0
_stub_curl
export FAKE_WP_SHA="cafe123"
export FAKE_PHP_HASH="deadbeef"
run phpvm_wp_cli
[ "$status" -ne 0 ]
[[ "$output" == *"SHA-512 mismatch"* ]]
[ ! -f "$PHPVM_DIR/wp-cli.phar" ]
[ ! -f "$PHPVM_BIN/wp" ]
}

# ---------- phpvm_fix_ini ----------

@test "fix-ini: errors when no active version" {
Expand Down Expand Up @@ -169,6 +234,13 @@ EOF
[[ "$output" == *"No active PHP version"* ]]
}

@test "dispatch: 'phpvm wp-cli' routes to phpvm_wp_cli" {
eval "_phpvm_current_version() { echo ''; }"
run phpvm wp-cli
[ "$status" -ne 0 ]
[[ "$output" == *"No active PHP version"* ]]
}

@test "dispatch: 'phpvm fix-ini' routes to phpvm_fix_ini" {
eval "_phpvm_current_version() { echo ''; }"
run phpvm fix-ini
Expand Down
69 changes: 69 additions & 0 deletions tests/windows/WpCli.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
Describe 'Invoke-WpCli' {
BeforeAll {
$env:PHPVM_DIR = Join-Path $TestDrive '.phpvm'
New-Item -ItemType Directory -Path $env:PHPVM_DIR -Force | Out-Null
. $PSScriptRoot/Common.ps1

# Test double for php.exe: any invocation prints $env:FAKE_PHP_HASH,
# standing in for hash_file('sha512', ...).
$script:FakePhp = Join-Path $TestDrive 'php.bat'
"@echo off`r`necho:%FAKE_PHP_HASH%" | Set-Content $FakePhp -Encoding ASCII
}

AfterAll {
Remove-Item Env:PHPVM_DIR -ErrorAction SilentlyContinue
Remove-Item Env:FAKE_PHP_HASH -ErrorAction SilentlyContinue
Remove-Item Env:PHPVM_SKIP_HASH -ErrorAction SilentlyContinue
}

BeforeEach {
$env:FAKE_PHP_HASH = 'cafe123'
Remove-Item Env:PHPVM_SKIP_HASH -ErrorAction SilentlyContinue
Remove-Item "$PHPVM_DIR\wp-cli.phar", "$PHPVM_BIN\wp.bat" -Force -ErrorAction SilentlyContinue

Mock -CommandName Get-PHPBuildInfo -MockWith { @{ Exe = $FakePhp } }
Mock -CommandName Invoke-WebRequest -MockWith { 'fake phar' | Set-Content $OutFile }
Mock -CommandName Get-WebString -MockWith { "$env:FAKE_PHP_HASH wp-cli.phar" }
Mock -CommandName Write-Err -MockWith {}
Mock -CommandName Write-Warn -MockWith {}
}

It 'Downloads the phar, verifies the hash, and writes the wp.bat shim' {
Invoke-WpCli

Test-Path "$PHPVM_DIR\wp-cli.phar" | Should -BeTrue
Test-Path "$PHPVM_BIN\wp.bat" | Should -BeTrue
Get-Content "$PHPVM_BIN\wp.bat" -Raw | Should -Match ([regex]::Escape("$PHPVM_DIR\wp-cli.phar"))
Should -Invoke Get-WebString -Times 1
Should -Invoke Write-Err -Times 0
}

It 'Is a no-op when the shim already exists' {
New-Item -ItemType Directory -Path $PHPVM_BIN -Force | Out-Null
'@echo off' | Set-Content "$PHPVM_BIN\wp.bat"

Invoke-WpCli

Should -Invoke Write-Warn -Times 1 -ParameterFilter { $m -like '*already installed*' }
Should -Invoke Invoke-WebRequest -Times 0
}

It 'Removes the phar and writes no shim on hash mismatch' {
Mock -CommandName Get-WebString -MockWith { 'deadbeef wp-cli.phar' }

Invoke-WpCli

Should -Invoke Write-Err -Times 1 -ParameterFilter { $m -like 'SHA-512 mismatch*' }
Test-Path "$PHPVM_DIR\wp-cli.phar" | Should -BeFalse
Test-Path "$PHPVM_BIN\wp.bat" | Should -BeFalse
}

It 'Skips verification when PHPVM_SKIP_HASH is set' {
$env:PHPVM_SKIP_HASH = '1'

Invoke-WpCli

Should -Invoke Get-WebString -Times 0
Test-Path "$PHPVM_BIN\wp.bat" | Should -BeTrue
}
}
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.10.0
1.11.0
2 changes: 1 addition & 1 deletion windows/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

$PHPVM_VERSION = "1.10.0"
$PHPVM_VERSION = "1.11.0"
$PHPVM_DIR = if ($env:PHPVM_DIR) { $env:PHPVM_DIR } else { "$env:USERPROFILE\.phpvm" }
$PHPVM_BIN = "$PHPVM_DIR\bin"

Expand Down
Loading
Loading