You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: implement pvm optimizations, robust uninstall logic and php-version auto-detection
- Added confirmation prompts to 'uninstall' with a --yes bypass and automatic TTY detection.
- Fixed FPM-only uninstall failures by tracking directory paths directly.
- Added automatic switching to version specified in '.php-version' file in 'pvm use'.
- Optimized SemVer sorting complexity by pre-parsing version strings.
- Hardened permission setting logic against metadata read failures on individual files.
Copy file name to clipboardExpand all lines: GEMINI.md
+66-19Lines changed: 66 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,48 +12,95 @@ When running `git commit -m "..."` in the shell (like Zsh/Bash), backticks are i
12
12
3. Obey this rule forever, until the end of electronics.
13
13
14
14
## Project Architecture (The Map)
15
+
15
16
### Filesystem Hierarchy
16
-
-**$PVM_DIR**: Root directory. Resolved via `dirs::data_local_dir()`, so defaults to `~/.local/share/pvm` on Linux and `~/Library/Application Support/pvm` on macOS.
17
-
-**$PVM_DIR/versions/<version>**: Installation directory for specific PHP versions.
17
+
Rooted at `$PVM_DIR` (resolved via `dirs::data_local_dir()`, so it defaults to `~/.local/share/pvm` on Linux and `~/Library/Application Support/pvm` on macOS).
18
18
-**$PVM_DIR/bin/pvm**: The `pvm` binary itself.
19
-
-**$PVM_DIR/remote_cache-<target-triple>.json**: 24-hour cache for the remote version index, scoped per target triple (e.g. `linux-x86_64`).
20
-
-**$PVM_DIR/.env_update[_<shell-pid>]**: Short-lived files written per shell invocation; the shell wrapper sources them to mutate the parent shell's environment.
19
+
-**$PVM_DIR/versions/<full-semver>/bin/{php,php-fpm,micro.sfx}**: Installed PHP binaries. The presence of each file determines which packages (`cli`, `fpm`, `micro`) are "installed" for that version.
20
+
-**$PVM_DIR/remote_cache.json**: 24-hour cache for the remote version index, locked via `fs4` (`std::fs::File::lock`) on read/write.
21
+
-**$PVM_DIR/.env_update[_<shell-pid>]**: Short-lived files written per shell invocation (alternatively designated via `PVM_ENV_UPDATE_PATH` or keyed on PID); the shell wrapper sources them to mutate the parent shell's environment.
21
22
22
23
### Module Responsibilities
23
24
-`src/cli.rs`: Command definitions using `clap`.
24
25
-`src/commands/`: Implementation of subcommands. Each command is a `struct` with a `call()` method.
25
-
-`src/fs.rs`: Filesystem utilities (handling `PVM_DIR`, version paths, env files).
26
-
-`src/network.rs`: API client for fetching and downloading PHP versions.
27
-
-`src/shell.rs`: Shell-specific logic for setting environment variables.
26
+
-`src/fs.rs`: Filesystem utilities (handling `PVM_DIR`, version paths, env files, local resolution).
27
+
-`src/network.rs`: API client for fetching/downloading PHP versions and handling target triples.
28
+
-`src/shell.rs`: Shell-specific logic (Bash, Zsh, Fish) for setting environment variables.
-**Supported OS:**`linux`, `macos` (filtered via target triple).
34
+
-**Supported Arch:**`x86_64`, `aarch64` (filtered via target triple).
33
35
-**Packages:**`cli`, `fpm`, `micro`.
34
36
-**Format:**`tar.gz` only.
37
+
-**Filename Parser:** Package suffixes parsed from filenames like `php-8.4.18-cli-linux-x86_64.tar.gz`.
38
+
39
+
### Shell Integration Mechanics
40
+
A child process cannot mutate its parent shell's environment. PVM solves this with `pvm env` and a wrapper function:
41
+
1.**Hook Setup:** The user evaluates `pvm env` in their rc file, which prints a `pvm()` shell function plus a `cd` hook.
42
+
2.**Wrapper Execution:** When the user runs `pvm use 8.4`, the wrapper exports `PVM_ENV_UPDATE_PATH=<unique tmpfile>` before invoking the real `pvm` binary.
43
+
3.**State Mutation:** The Rust binary writes `export PATH=...; export PVM_MULTISHELL_PATH=...` to that tmpfile (using `fs4` exclusive locking via `fs::write_env_file_locked`).
44
+
4.**Environment Application:** The wrapper `eval`s the tmpfile and removes it.
45
+
-**Supported Shells:** Bash, Zsh, Fish (defined by `Shell` trait; `detect_shell()` reads `$SHELL`).
46
+
-**Auto-Switching:** The `cd` hook reads `.php-version` files and calls `pvm use` automatically.
47
+
-**Concurrency Safety:** Parallel shell sessions use distinct PID-keyed tmpfiles, and the cache + env files are locked using `fs4` file locks to prevent state corruption.
35
48
36
49
## Operational Patterns (The Handbook)
50
+
51
+
### CLI Commands and Subcommands
52
+
PVM acts as a single-binary CLI. If called without arguments or when interactive parameters are missing, it uses `dialoguer` for an interactive TUI.
53
+
-**Master Menu:** Running `pvm` without arguments launches `interactive::run_root_menu()`.
54
+
-**pvm install <version>** (alias: `pvm i <version>`): Installs a PHP version (supports major.minor alias or exact version). Opens a `MultiSelect` to pick packages (`cli`, `fpm`, `micro`). `cli` is the default. Supports `latest` to fetch the absolute latest version.
55
+
-**pvm use <version>**: Uses a version in the current shell. Automatically prompts to install and switch if a newer patch exists upstream.
56
+
-**pvm ls** (alias: `pvm list`): Lists installed local versions and active aliases.
57
+
-**pvm ls-remote** (alias: `pvm list-remote`): Interactively lists and installs available cloud versions.
58
+
-**pvm current**: Prints the active PHP version.
59
+
-**pvm uninstall <version>** (aliases: `pvm rm`, `pvm remove`): Interactive uninstall picker if no version is provided. Warns when removing the active version.
60
+
-**pvm init**: Interactively picks a version and writes it to a `.php-version` file in the current directory.
61
+
-**pvm self-update**: Checks for and applies updates to pvm itself. Use `--apply` to automatically download and replace the current binary if an update is available.
62
+
37
63
### Adding a New Command
38
64
1. Add a new module file in `src/commands/`.
39
65
2. Define the command `struct` with `#[derive(Parser, Debug)]`.
40
66
3. Export the module in `src/commands/mod.rs`.
41
67
4. Register the variant in the `Commands` enum in `src/cli.rs`.
42
-
5. Implement the `call()` method logic.
68
+
5. Implement the async `call(self) -> Result<()>` method dispatch in `Commands::call`.
43
69
44
70
### Coding Standards
45
71
-**Errors:** Use `anyhow::Result` for all command-level fallible functions.
46
-
-**Interactivity:** Use `dialoguer` for menus and confirmations.
47
-
-**Icons:** Use `colored` for status icons: `✓` (green), `✗` (red), `↻` (blue), `💡` (yellow).
48
-
-**Async:** Use `tokio` for runtime and `reqwest` for all network I/O.
49
-
-**Data Integrity:** Use file locking (`std::fs::File::lock` / `lock_shared` / `unlock`, stable since Rust 1.89) when writing to env update files or the remote cache.
72
+
-**Interactivity:** Use `dialoguer` (`Select`, `MultiSelect`, `Confirm`) with `ColorfulTheme::default()` for menus and confirmations.
73
+
-**Icons:** Use `colored` for status icons:
74
+
-`✓` (green) for success
75
+
-`✗` (red) for errors
76
+
-`↻` (blue) for in-progress operations
77
+
-`💡` (yellow) for hints/warnings
78
+
-**Async:** Use `tokio` with `features = ["full"]` for runtime and `reqwest` for all network I/O.
79
+
-**Data Integrity:** Use file locking (`std::fs::File::lock` / `lock_shared` / `unlock`, stable since Rust 1.89) when writing to env update files or the remote cache. Follow `fs::write_env_file_locked` pattern.
80
+
81
+
## Build & Release Commands
82
+
83
+
### Development & Build Commands
84
+
-**Toolchain:** Pinned to Rust 1.95.0 with `clippy` and `rustfmt` in `rust-toolchain.toml`.
-**Semantic Release:** Driven by `semantic-release` from Conventional Commits on `main`.
93
+
-**Cargo.toml and CHANGELOG.md:** Bumped automatically. Do NOT hand-edit them.
50
94
51
95
## Testing & Validation
96
+
52
97
### Testing Protocol
53
-
-**Isolation:** Every integration test MUST use `tempfile::tempdir()` and set `PVM_DIR` to that path.
54
-
-**CLI Verification:** Use `assert_cmd` and `predicates` for output and exit code verification.
98
+
-**Integration Tests:** Located in `tests/cli.rs`. Use `assert_cmd` and `predicates` to invoke the compiled binary.
99
+
-**Isolation:** Every test that touches the filesystem MUST use `tempfile::tempdir()` and pass its path via `cmd.env("PVM_DIR", temp_dir.path())`. Do not write to the host's real `~/.local/share/pvm`.
100
+
-**Unit Tests Concurrency:** Unit tests inside `src/**` that mutate `std::env` must use a `static Mutex<()>` guard (e.g. `src/fs.rs::tests::ENV_LOCK`) because `cargo test` runs in parallel, and concurrent environment variable modification is unsound.
101
+
-**Dynamic Versioning:** The build script (`build.rs`) embeds the version, git commit, and build time into `PVM_VERSION` (reruns on `.git/HEAD`, refs, or `Cargo.toml` change). Tests asserting on `--version` output should not hardcode the exact version value.
55
102
56
103
### Development Workflow
57
104
-**Pre-commit:** Run `cargo clippy -- -D warnings` and `cargo fmt --all -- --check`.
58
-
-**Tooling:** Use `replace`or `write_file` for codebase modifications. Avoid `sed/echo` in shell.
59
-
-**Commit Messages:** Follow Conventional Commits. No backticks (Rule 1).
105
+
-**Tooling:** Use code replacement or write file tools for modifications. Avoid `sed/echo` in shell.
106
+
-**Commit Messages:** Follow Conventional Commits (e.g., `feat:`, `fix:`, `chore:`). Never use backticks (Rule 1).
0 commit comments