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
15 changes: 9 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

## [Unreleased]

## [v1.12.0](https://github.com/Thavarshan/phpvm/compare/v1.11.0...v1.12.0) - 2026-05-20
## [v1.12.1](https://github.com/Thavarshan/phpvm/compare/v1.12.0...v1.12.1) - 2026-05-24

### Added

- **`latest-remote` keyword:** Install the newest available remote PHP version directly from package manager repos with `phpvm install latest-remote`.
- **`latest-available` alias:** Alias for `latest-remote`.
- **`self-update` command:** Add `phpvm self-update` to automatically update phpvm to the latest stable release and print the updated version.

## [v1.12.0](https://github.com/Thavarshan/phpvm/compare/v1.11.0...v1.12.0) - 2026-05-20

### Added

- **`latest-remote` keyword:** Install the newest available remote PHP version directly from package manager repos with `phpvm install latest-remote`.
- **`latest-available` alias:** Alias for `latest-remote`.

- **`exec` command:** Run a command with a specific PHP version without globally switching. Executes in a subshell to isolate PATH changes from the current shell. Usage: `phpvm exec <version> <command> [args...]`.
- **`run` command:** Sugar for `phpvm exec <version> php <script>`. Usage: `phpvm run <version> [script] [args...]`.
- **`ls-remote` command:** List available PHP versions from system package manager repositories (Homebrew, apt, dnf, yum, pacman). Supports optional pattern filtering (e.g., `phpvm ls-remote 8.2`).
Expand Down Expand Up @@ -317,7 +320,7 @@

## [v1.3.0](https://github.com/Thavarshan/phpvm/compare/v1.2.0...v1.3.0) - 2025-05-11

## Added
### Added

- Added `system` command to easily switch back to system PHP version
- Added timestamps to all log messages for better traceability and debugging
Expand All @@ -330,7 +333,7 @@
- Added ability to run self-tests with `phpvm test` command
- Added debugging capability via `DEBUG=true` environment variable

## Changed
### Changed

- Changed logging format to include timestamps and log levels
- Changed sudo handling to use a centralized helper function
Expand All @@ -341,7 +344,7 @@
- Improved bash/zsh shell compatibility with better sourcing logic
- Improved code organization and reduced duplication with helper functions

## Fixed
### Fixed

- Fixed shell crash issue when sourcing in zsh with p10k theme
- Fixed path expansion issues in Ubuntu bashrc configurations
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

```sh
$ phpvm version
phpvm version 1.11.0
phpvm version 1.12.1

PHP Version Manager for macOS and Linux
Author: Jerome Thayananthajothy <tjthavarshan@gmail.com>
Expand All @@ -41,6 +41,7 @@ PHP 8.1.13
- **Run commands with a specific PHP version** without globally switching (`phpvm exec`, `phpvm run`).
- **List available remote PHP versions** from your system package manager (`phpvm ls-remote`).
- **Install the latest available remote PHP version** with `phpvm install latest-remote`.
- **Update phpvm in place** using `phpvm self-update` to pull the latest stable script version.
- **Resolve version descriptors and aliases** to installed version numbers (`phpvm resolve`).
- Auto-switch PHP versions based on project `.phpvmrc` (configurable depth via `PHPVM_PHPVMRC_MAX_DEPTH`).
- Automatic directory-based switching via built-in cd hook (`PROMPT_COMMAND` for bash, `chpwd` for zsh).
Expand Down Expand Up @@ -122,6 +123,7 @@ If the installation was successful, it should output the path to `phpvm`.
| `phpvm list` or `phpvm ls` | List all installed PHP versions |
| `phpvm ls-remote [pattern]` | List available remote PHP versions |
| `phpvm install latest-remote` | Install the latest remote PHP version available |
| `phpvm self-update` | Update phpvm to the latest stable release |
| `phpvm resolve <version\|alias>` | Resolve a version descriptor to an installed version |
| `phpvm alias [name] [ver]` | Create, update, or list version aliases |
| `phpvm unalias <name>` | Remove version alias |
Expand Down
136 changes: 134 additions & 2 deletions phpvm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# phpvm - A PHP Version Manager for macOS and Linux
# Author: Jerome Thayananthajothy (tjthavarshan@gmail.com)
# Version: 1.10.0
# Version: 1.12.1
#
# IMPORTANT: This script is written for bash and uses bashisms (arrays, process substitution, etc.)
# For sourcing into your shell, use bash only. Zsh users should run phpvm via:
Expand All @@ -11,7 +11,7 @@

# shellcheck disable=SC2155 # Allow declare and assign on same line for better readability

PHPVM_VERSION="1.12.0"
PHPVM_VERSION="1.12.1"

# Test mode flag
PHPVM_TEST_MODE="${PHPVM_TEST_MODE:-false}"
Expand Down Expand Up @@ -2623,6 +2623,7 @@ Usage:
phpvm alias [name] [ver] Create, update, or list version aliases
phpvm unalias <name> Remove version alias
phpvm cache <dir|clear> Manage cache directory
phpvm self-update Update phpvm to the latest stable version
phpvm help Show this help message
phpvm info Show system information for debugging
phpvm version Show version information
Expand Down Expand Up @@ -2678,6 +2679,129 @@ Usage: phpvm help
EOF
}

phpvm_get_self_update_target() {
local script_path
if [ -n "${PHPVM_SELF_UPDATE_DEST:-}" ]; then
script_path="$PHPVM_SELF_UPDATE_DEST"
else
script_path="${BASH_SOURCE[0]:-$0}"
fi

if [ -L "$script_path" ] && command_exists readlink; then
local link
link=$(command readlink "$script_path")
if [ "${link#/}" != "$link" ]; then
script_path="$link"
else
local dir
dir=$(cd "$(dirname "$script_path")" 2> /dev/null && pwd)
script_path="$dir/$link"
fi
fi

printf '%s
' "$script_path"
}

phpvm_download_url_to_file() {
local url="$1"
local output="$2"

if command_exists curl; then
command curl --fail --silent --location "$url" > "$output"
return "$?"
fi

if command_exists wget; then
command wget --quiet --output-document="$output" "$url"
return "$?"
fi

phpvm_err "curl or wget is required for self-update."
return "$PHPVM_EXIT_ERROR"
}

phpvm_extract_version_from_file() {
local file="$1"
local version

version=$(command grep -Eo 'PHPVM_VERSION="[0-9]+\.[0-9]+\.[0-9]+"' "$file" 2> /dev/null | command head -1 | command sed 's/PHPVM_VERSION="//;s/"$//')
if [ -z "$version" ]; then
return "$PHPVM_EXIT_ERROR"
fi

printf '%s
' "$version"
return "$PHPVM_EXIT_SUCCESS"
}

phpvm_self_update() {
local source_url="${PHPVM_SELF_UPDATE_URL:-https://raw.githubusercontent.com/Thavarshan/phpvm/main/phpvm.sh}"
local tmp_file
local remote_version
local target_script

target_script=$(phpvm_get_self_update_target) || return "$PHPVM_EXIT_ERROR"
if [ -z "$target_script" ]; then
phpvm_err "Unable to determine phpvm script path for self-update."
return "$PHPVM_EXIT_ERROR"
fi

tmp_file=$(mktemp) || {
phpvm_err "Failed to create temporary file for self-update."
return "$PHPVM_EXIT_FILE_ERROR"
}

if phpvm_is_test_mode && [ -n "${PHPVM_SELF_UPDATE_TEST_SOURCE:-}" ]; then
if [ ! -f "$PHPVM_SELF_UPDATE_TEST_SOURCE" ]; then
rm -f "$tmp_file"
phpvm_err "Self-update test source not found: $PHPVM_SELF_UPDATE_TEST_SOURCE"
return "$PHPVM_EXIT_FILE_ERROR"
fi
command cp "$PHPVM_SELF_UPDATE_TEST_SOURCE" "$tmp_file"
else
if ! phpvm_download_url_to_file "$source_url" "$tmp_file"; then
rm -f "$tmp_file"
phpvm_err "Failed to download phpvm update from $source_url"
return "$PHPVM_EXIT_ERROR"
fi
fi

remote_version=$(phpvm_extract_version_from_file "$tmp_file") || {
rm -f "$tmp_file"
phpvm_err "Failed to determine remote phpvm version."
return "$PHPVM_EXIT_ERROR"
}

if [ "$remote_version" = "$PHPVM_VERSION" ]; then
rm -f "$tmp_file"
phpvm_echo "You are already on the latest version: v$PHPVM_VERSION."
return "$PHPVM_EXIT_SUCCESS"
fi

phpvm_echo "Updating phpvm..."

if [ ! -w "$target_script" ] && command_exists sudo; then
if ! run_with_sudo cp "$tmp_file" "$target_script"; then
rm -f "$tmp_file"
phpvm_err "Failed to update phpvm."
return "$PHPVM_EXIT_ERROR"
fi
else
if ! command cp "$tmp_file" "$target_script"; then
rm -f "$tmp_file"
phpvm_err "Failed to update phpvm."
return "$PHPVM_EXIT_ERROR"
fi
fi

chmod +x "$target_script" 2> /dev/null || true
rm -f "$tmp_file"

phpvm_echo "phpvm successfully updated to the latest version: v$remote_version."
return "$PHPVM_EXIT_SUCCESS"
}

# Print system information for debugging
print_system_info() {
phpvm_echo "System Information:"
Expand Down Expand Up @@ -3283,6 +3407,10 @@ main() {
print_version
exit "$PHPVM_EXIT_SUCCESS"
;;
self-update)
phpvm_self_update
exit "$?"
;;
unload)
phpvm_unload
exit "$?"
Expand Down Expand Up @@ -3405,6 +3533,10 @@ main() {
phpvm_cache "$@"
exit "$?"
;;
self-update)
phpvm_self_update
exit "$?"
;;
*)
phpvm_err "Unknown command: $command"
print_help
Expand Down
6 changes: 0 additions & 6 deletions release-notes-1.12.0.md

This file was deleted.

77 changes: 77 additions & 0 deletions tests/01_core.bats
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,83 @@ load test_helper
[[ "$output" =~ "Usage:" ]]
}

@test "phpvm self-update reports already latest version" {
local remote_file="$TEST_DIR/phpvm-latest.sh"
cat > "$remote_file" <<'EOF'
#!/bin/bash
PHPVM_VERSION="1.12.1"
EOF

run env PHPVM_TEST_MODE=true PHPVM_SELF_UPDATE_TEST_SOURCE="$remote_file" PHPVM_SELF_UPDATE_DEST="$TEST_DIR/phpvm-self-update-target.sh" bash "$BATS_TEST_DIRNAME/../phpvm.sh" self-update
[ "$status" -eq 0 ]
[[ "$output" =~ "You are already on the latest version: v1.12.1." ]]
}

@test "phpvm self-update replaces script when newer version is available" {
local remote_file="$TEST_DIR/phpvm-updated.sh"
local target_file="$TEST_DIR/phpvm-self-update-target.sh"
cat > "$remote_file" <<'EOF'
#!/bin/bash
PHPVM_VERSION="1.12.2"
EOF
touch "$target_file"

run env PHPVM_TEST_MODE=true PHPVM_SELF_UPDATE_TEST_SOURCE="$remote_file" PHPVM_SELF_UPDATE_DEST="$target_file" bash "$BATS_TEST_DIRNAME/../phpvm.sh" self-update
[ "$status" -eq 0 ]
[[ "$output" =~ "phpvm successfully updated to the latest version: v1.12.2." ]]
[ "$(grep -oE 'PHPVM_VERSION="[0-9]+\.[0-9]+\.[0-9]+"' "$target_file")" = "PHPVM_VERSION=\"1.12.2\"" ]
}

@test "phpvm self-update downloads from remote URL and updates script" {
if ! command -v python3 > /dev/null 2>&1; then
skip "python3 is required for remote self-update integration test"
fi

local server_root="$TEST_DIR/http-server"
local server_log="$TEST_DIR/http-server.log"
local target_file="$TEST_DIR/phpvm-self-update-http-target.sh"
mkdir -p "$server_root"

cat > "$server_root/phpvm.sh" <<'EOF'
#!/bin/bash
PHPVM_VERSION="1.12.3"
EOF
chmod +x "$server_root/phpvm.sh"

python3 - "$server_root" <<'PY' > "$server_log" 2>&1 &
import http.server
import socketserver
import os
import sys

os.chdir(sys.argv[1])
with socketserver.TCPServer(("127.0.0.1", 0), http.server.SimpleHTTPRequestHandler) as httpd:
print(httpd.server_address[1], flush=True)
sys.stdout.flush()
httpd.serve_forever()
PY
local server_pid=$!
sleep 1

local port
port=$(head -n 1 "$server_log" | tr -d '[:space:]')
if [ -z "$port" ]; then
kill "$server_pid" > /dev/null 2>&1 || true
wait "$server_pid" 2>/dev/null || true
echo "Failed to start local HTTP server" >&2
return 1
fi

touch "$target_file"

trap 'kill "$server_pid" > /dev/null 2>&1 || true; wait "$server_pid" 2>/dev/null || true' RETURN

run env PHPVM_TEST_MODE=true PHPVM_SELF_UPDATE_URL="http://127.0.0.1:$port/phpvm.sh" PHPVM_SELF_UPDATE_DEST="$target_file" bash "$BATS_TEST_DIRNAME/../phpvm.sh" self-update
[ "$status" -eq 0 ]
[[ "$output" =~ "phpvm successfully updated to the latest version: v1.12.3." ]]
[ "$(grep -oE 'PHPVM_VERSION="[0-9]+\.[0-9]+\.[0-9]+"' "$target_file")" = "PHPVM_VERSION=\"1.12.3\"" ]
}

@test "sanitize_input rejects dangerous characters" {
run sanitize_input "8.1; rm -rf /"
[ "$status" -ne 0 ]
Expand Down
Loading