Initial commit

This commit is contained in:
2025-07-29 12:47:50 +03:00
commit 785a158451
25 changed files with 2428 additions and 0 deletions

13
.editorconfig Normal file
View File

@@ -0,0 +1,13 @@
# EditorConfig is awesome: https://editorconfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
max_line_length = 120

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
* @ivuorinen

145
.github/CODE_OF_CONDUCT.md vendored Normal file
View File

@@ -0,0 +1,145 @@
# Citizen Code of Conduct
## 1. Purpose
A primary goal of @ivuorinen's repositories is to be inclusive to the largest
number of contributors, with the most varied and diverse backgrounds possible.
As such, we are committed to providing a friendly, safe and welcoming
environment for all, regardless of gender, sexual orientation, ability,
ethnicity, socioeconomic status, and religion (or lack thereof).
This code of conduct outlines our expectations for all those who participate in
our community, as well as the consequences for unacceptable behavior.
We invite all those who participate in @ivuorinen's repositories to help us
create safe and positive experiences for everyone.
## 2. Open [Source/Culture/Tech] Citizenship
A supplemental goal of this Code of Conduct is to increase
open [source/culture/tech] citizenship by encouraging participants to recognize
and strengthen the relationships between our actions and their effects on our
community.
Communities mirror the societies in which they exist and positive action is
essential to counteract the many forms of inequality and abuses of power that
exist in society.
If you see someone who is making an extra effort to ensure our community is
welcoming, friendly, and encourages all participants to contribute to the
fullest extent, we want to know.
## 3. Expected Behavior
The following behaviors are expected and requested of all community members:
* Participate in an authentic and active way. In doing so, you contribute to the
health and longevity of this community.
* Exercise consideration and respect in your speech and actions.
* Attempt collaboration before conflict.
* Refrain from demeaning, discriminatory, or harassing behavior and speech.
* Be mindful of your surroundings and of your fellow participants. Alert
community leaders if you notice a dangerous situation, someone in distress, or
violations of this Code of Conduct, even if they seem inconsequential.
* Remember that community event venues may be shared with members of the public;
please be respectful to all patrons of these locations.
## 4. Unacceptable Behavior
The following behaviors are considered harassment and are unacceptable within
our community:
* Violence, threats of violence or violent language directed against another
person.
* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory
jokes and language.
* Posting or displaying sexually explicit or violent material.
* Posting or threatening to post other people's personally identifying
information ("doxing").
* Personal insults, particularly those related to gender, sexual orientation,
race, religion, or disability.
* Inappropriate photography or recording.
* Inappropriate physical contact. You should have someone's consent before
touching them.
* Unwelcome sexual attention. This includes, sexualized comments or jokes;
inappropriate touching, groping, and unwelcomed sexual advances.
* Deliberate intimidation, stalking or following (online or in person).
* Advocating for, or encouraging, any of the above behavior.
* Sustained disruption of community events, including talks and presentations.
## 5. Weapons Policy
No weapons will be allowed at @ivuorinen's repositories events, community
spaces, or in other spaces covered by the scope of this Code of Conduct. Weapons
include but are not limited to guns, explosives (including fireworks), and large
knives such as those used for hunting or display, as well as any other item used
for the purpose of causing injury or harm to others. Anyone seen in possession
of one of these items will be asked to leave immediately, and will only be
allowed to return without the weapon. Community members are further expected to
comply with all state and local laws on this matter.
## 6. Consequences of Unacceptable Behavior
Unacceptable behavior from any community member, including sponsors and those
with decision-making authority, will not be tolerated.
Anyone asked to stop unacceptable behavior is expected to comply immediately.
If a community member engages in unacceptable behavior, the community organizers
may take any action they deem appropriate, up to and including a temporary ban
or permanent expulsion from the community without warning (and without refund in
the case of a paid event).
## 7. Reporting Guidelines
If you are subject to or witness unacceptable behavior, or have any other
concerns, please notify a community organizer as soon as possible:
<ismo@ivuorinen.net>
Additionally, community organizers are available to help community members
engage with local law enforcement or to otherwise help those experiencing
unacceptable behavior feel safe. In the context of in-person events, organizers
will also provide escorts as desired by the person experiencing distress.
## 8. Addressing Grievances
If you feel you have been falsely or unfairly accused of violating this Code of
Conduct, you should notify @ivuorinen with a concise description of your
grievance. Your grievance will be handled in accordance with our existing
governing policies.
## 9. Scope
We expect all community participants (contributors, paid or otherwise; sponsors;
and other guests) to abide by this Code of Conduct in all community
venues--online and in-person--as well as in all one-on-one communications
pertaining to community business.
This code of conduct and its related procedures also applies to unacceptable
behavior occurring outside the scope of community activities when such behavior
has the potential to adversely affect the safety and well-being of community
members.
## 10. Contact info
@ivuorinen
<ismo@ivuorinen.net>
## 11. License and attribution
The Citizen Code of Conduct is distributed by [Stumptown Syndicate][stumptown]
under a [Creative Commons Attribution-ShareAlike license][cc-by-sa].
Portions of text derived from the [Django Code of Conduct][django] and
the [Geek Feminism Anti-Harassment Policy][geek-feminism].
* _Revision 2.3. Posted 6 March 2017._
* _Revision 2.2. Posted 4 February 2016._
* _Revision 2.1. Posted 23 June 2014._
* _Revision 2.0, adopted by the [Stumptown Syndicate][stumptown] board on 10
January 2013. Posted 17 March 2013._
[stumptown]: https://github.com/stumpsyn
[cc-by-sa]: https://creativecommons.org/licenses/by-sa/3.0/
[django]: https://www.djangoproject.com/conduct/
[geek-feminism]: http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ivuorinen
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ivuorinen
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

6
.github/renovate.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>ivuorinen/renovate-config"
]
}

46
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: 'CodeQL'
on:
push:
branches: ['main']
pull_request:
branches: ['main']
schedule:
- cron: '30 1 * * 0' # Run at 1:30 AM UTC every Sunday
merge_group:
permissions:
actions: read
contents: read
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
security-events: write
strategy:
fail-fast: false
matrix:
language: ['javascript'] # Add languages used in your actions
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Initialize CodeQL
uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
with:
languages: ${{ matrix.language }}
queries: security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
with:
category: '/language:${{matrix.language}}'

30
.github/workflows/pr-lint.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Lint Code Base
on:
push:
branches: [master, main]
pull_request:
branches: [master, main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions: read-all
jobs:
Linter:
name: PR Lint
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
statuses: write
contents: read
packages: read
steps:
- name: Run PR Lint
# https://github.com/ivuorinen/actions
uses: ivuorinen/actions/pr-lint@8476cd4675ea8210eadf4a267bbeb13bddea4e75 # 25.7.21

26
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Stale
on:
schedule:
- cron: '0 8 * * *' # Every day at 08:00
workflow_call:
workflow_dispatch:
permissions:
contents: read
packages: read
statuses: read
jobs:
stale:
name: 🧹 Clean up stale issues and PRs
runs-on: ubuntu-latest
permissions:
contents: write # only for delete-branch option
issues: write
pull-requests: write
steps:
- uses: ivuorinen/actions/stale@8476cd4675ea8210eadf4a267bbeb13bddea4e75 # 25.7.21

41
.github/workflows/sync-labels.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Sync Labels
on:
push:
branches:
- main
- master
paths:
- '.github/labels.yml'
- '.github/workflows/sync-labels.yml'
schedule:
- cron: '34 5 * * *' # Run every day at 05:34 AM UTC
workflow_call:
workflow_dispatch:
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions: read-all
jobs:
labels:
name: ♻️ Sync Labels
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
issues: write
steps:
- name: ⤵️ Checkout Repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: ⤵️ Sync Latest Labels Definitions
uses: ivuorinen/actions/sync-labels@8476cd4675ea8210eadf4a267bbeb13bddea4e75 # 25.7.21

134
.gitignore vendored Normal file
View File

@@ -0,0 +1,134 @@
.php-cs-fixer.cache
.php-cs-fixer.php
composer.phar
/vendor/
.phpunit.result.cache
.phpunit.cache
/app/phpunit.xml
/phpunit.xml
/build/
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
pids
*.pid
*.seed
*.pid.lock
lib-cov
coverage
*.lcov
.nyc_output
.grunt
bower_components
.lock-wscript
build/Release
node_modules/
jspm_packages/
web_modules/
*.tsbuildinfo
.npm
.eslintcache
.stylelintcache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
.node_repl_history
*.tgz
.yarn-integrity
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
.cache
.parcel-cache
.next
out
.nuxt
dist
.cache/
.vuepress/dist
.temp
.docusaurus
.serverless/
.fusebox/
.dynamodb/
.tern-port
.vscode-test
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
Session.vim
Sessionx.vim
.netrwhist
*~
tags
[._]*.un~
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea/**/aws.xml
.idea/**/contentModel.xml
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
.idea/**/gradle.xml
.idea/**/libraries
cmake-build-*/
.idea/**/mongoSettings.xml
*.iws
out/
.idea_modules/
atlassian-ide-plugin.xml
.idea/replstate.xml
.idea/sonarlint/
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
.idea/httpRequests
.idea/caches/build_file_checksums.ser
npm-debug.log
yarn-error.log
bootstrap/compiled.php
app/storage/
public/storage
public/hot
public_html/storage
public_html/hot
storage/*.key
Homestead.yaml
Homestead.json
/.vagrant
/node_modules
/.pnp
.pnp.js
/coverage
/.next/
/out/
/build
.DS_Store
*.pem
.env*.local
.vercel
next-env.d.ts

13
.markdownlint.json Normal file
View File

@@ -0,0 +1,13 @@
{
"default": true,
"MD013": {
"line_length": 200,
"code_blocks": false,
"tables": false
},
"MD024": {
"siblings_only": true
},
"MD033": false,
"MD041": false
}

35
.mega-linter.yml Normal file
View File

@@ -0,0 +1,35 @@
---
# Configuration file for MegaLinter
# See all available variables at
# https://megalinter.io/configuration/ and in linters documentation
APPLY_FIXES: all
SHOW_ELAPSED_TIME: false # Show elapsed time at the end of MegaLinter run
PARALLEL: true
VALIDATE_ALL_CODEBASE: true
FILEIO_REPORTER: false # Generate file.io report
GITHUB_STATUS_REPORTER: true # Generate GitHub status report
IGNORE_GENERATED_FILES: true # Ignore generated files
JAVASCRIPT_DEFAULT_STYLE: prettier # Default style for JavaScript
PRINT_ALPACA: false # Print Alpaca logo in console
SARIF_REPORTER: true # Generate SARIF report
SHOW_SKIPPED_LINTERS: false # Show skipped linters in MegaLinter log
DISABLE_LINTERS:
- REPOSITORY_DEVSKIM
ENABLE_LINTERS:
- YAML_YAMLLINT
- MARKDOWN_MARKDOWNLINT
- YAML_PRETTIER
- JSON_PRETTIER
- JAVASCRIPT_ES
- TYPESCRIPT_ES
YAML_YAMLLINT_CONFIG_FILE: .yamllint.yml
MARKDOWN_MARKDOWNLINT_CONFIG_FILE: .markdownlint.json
JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.json
TYPESCRIPT_ES_CONFIG_FILE: .eslintrc.json
FILTER_REGEX_EXCLUDE: >
(node_modules|\.automation/test|docs/json-schemas|\.github/workflows)

63
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,63 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: requirements-txt-fixer
- id: detect-private-key
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: check-case-conflict
- id: check-merge-conflict
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: check-symlinks
- id: check-toml
- id: check-xml
- id: check-yaml
args: [--allow-multiple-documents]
- id: end-of-file-fixer
- id: mixed-line-ending
args: [--fix=auto]
- id: pretty-format-json
args: [--autofix, --no-sort-keys]
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.44.0
hooks:
- id: markdownlint
args: [-c, .markdownlint.json, --fix]
- repo: https://github.com/adrienverge/yamllint
rev: v1.37.0
hooks:
- id: yamllint
- repo: https://github.com/scop/pre-commit-shfmt
rev: v3.11.0-1
hooks:
- id: shfmt
- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.10.0
hooks:
- id: shellcheck
args: ['--severity=warning']
- repo: https://github.com/rhysd/actionlint
rev: v1.7.7
hooks:
- id: actionlint
args: ['-shellcheck=']
- repo: https://github.com/renovatebot/pre-commit-hooks
rev: 39.227.2
hooks:
- id: renovate-config-validator
- repo: https://github.com/bridgecrewio/checkov.git
rev: '3.2.400'
hooks:
- id: checkov
args:
- '--quiet'

1
.shellcheckrc Normal file
View File

@@ -0,0 +1 @@
disable=SC2129

0
.yamlignore Normal file
View File

13
.yamllint.yml Normal file
View File

@@ -0,0 +1,13 @@
---
extends: default
rules:
line-length:
max: 200
level: warning
truthy:
check-keys: false
comments:
min-spaces-from-content: 1
trailing-spaces:
level: warning

152
CLAUDE.md Normal file
View File

@@ -0,0 +1,152 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
phpenv.fish is a PHP version manager for Fish Shell, similar to nvm for Node.js.
It provides fast PHP version switching, extension management, and automatic version detection from project files.
## Development Commands
### Testing Changes
Since this is a Fish shell plugin, test changes by:
```bash
# Reload the function after changes
source functions/phpenv.fish
# Test commands
phpenv help
phpenv versions
phpenv current
```
### Installation for Development
```bash
# Link the development version to Fish config
ln -sf $PWD/functions/phpenv.fish ~/.config/fish/functions/phpenv.fish
ln -sf $PWD/completions/phpenv.fish ~/.config/fish/completions/phpenv.fish
ln -sf $PWD/conf.d/phpenv.fish ~/.config/fish/conf.d/phpenv.fish
```
## Code Architecture
### Core Components
1. **Main Dispatcher (`functions/phpenv.fish`)**
- Entry point function that routes commands to internal functions
- All subcommands are implemented as `__phpenv_*` functions
- Version detection logic in `__phpenv_detect_version`
2. **Completions (`completions/phpenv.fish`)**
- Provides tab completions for all commands
- Fetches available versions dynamically from shivammathur/setup-php
3. **Configuration (`conf.d/phpenv.fish`)**
- Sets up Fish universal variables on load
- Handles PATH initialization
### Key Design Patterns
- **Performance Focus**: Direct directory checks instead of `brew list` (100-1000x faster)
- **Fish Universal Variables**: Used for configuration persistence
- **Homebrew Integration**: Uses shivammathur taps for PHP/extension installation
- **Version File Priority**: `.php-version` > `.tool-version` > `composer.json` > global > system
### Version Detection Flow
1. Check for `.php-version` file (exact version)
2. Check for `.tool-version` file (parse PHP line)
3. Check `composer.json` for PHP constraints (semver resolution)
4. Use global version from Fish universal variable
5. Fall back to system PHP
### Important Implementation Details
- All internal functions are prefixed with `__phpenv_`
- Version resolution supports semver constraints (^8.1, ~8.2.0, etc.)
- Extension management uses separate Homebrew tap
- Auto-switching uses Fish's `pwd` event handler
- Configuration stored in Fish universal variables with `PHPENV_` prefix
## Code Style Requirements
- Maximum line length: 120 characters (enforced by .editorconfig)
- Use LF line endings
- UTF-8 encoding
- Trim trailing whitespace
- Insert final newline
## PATH and Variable Management
### PATH State Tracking
- `PHPENV_ORIGINAL_PATH`: Stores initial PATH before any modifications
- `PHPENV_CURRENT_VERSION`: Tracks currently active PHP version
- `PHPENV_CURRENT_PATH`: Stores path to current PHP binary
- Use `phpenv use system` to restore original PATH
### Variable Scope Strategy
- **Universal variables** (`set -U`): Only `PHPENV_GLOBAL_VERSION` (persists across shells)
- **Session variables** (`set -g`): Configuration settings (per-shell session)
- **Local variables** (`set -l`): Function-scoped, automatically cleaned up
### Auto-Switch Debouncing
- `PHPENV_LAST_SWITCH_TIME`: Prevents excessive PATH changes on rapid directory changes
- 1-second minimum interval between auto-switches
- Early exit if already using correct version
## Common Tasks
### Adding a New Command
1. Add case in main `phpenv` function switch statement
2. Implement `__phpenv_<command>` function
3. Add completions in `completions/phpenv.fish`
4. Update help text in `__phpenv_help`
### Modifying Version Detection
- Edit `__phpenv_detect_version` function
- Maintain priority order of version sources
- Test with various project configurations
### Working with Homebrew Integration
- PHP versions: `shivammathur/php/php@<version>`
- Extensions: `shivammathur/extensions/<extension>@<php-version>`
- Check formula existence before operations
### Performance Optimizations
#### Caching System
- `__phpenv_version_cache`: 5-minute cache for API version data
- `__phpenv_cellar_cache`: Permanent cache for Homebrew Cellar path
- Reduces network calls and filesystem operations
#### Unified Helper Functions
- `__phpenv_parse_version_field`: Single function for all jq parsing (eliminates 9+ duplicated calls)
- `__phpenv_ensure_taps`: Unified Homebrew tap management
- `__phpenv_get_available_extensions`: Shared extension listing logic
### PATH Management Best Practices
- Always check `PHPENV_ORIGINAL_PATH` exists before modification
- Use debouncing for automatic operations
- Validate PHP paths before setting
- Provide restoration mechanism (`phpenv use system`)
- Clean up temporary variables in error cases
### Code Organization Principles
- Cache expensive operations (API calls, filesystem checks)
- Unify repeated patterns into helper functions
- Use session variables instead of universal where possible
- Minimize network requests and subprocess calls

20
LICENSE Normal file
View File

@@ -0,0 +1,20 @@
MIT License Copyright (c) 2025 Ismo Vuorinen
Permission is hereby granted, free of charge, to any person obtaining a copy
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

21
LICENSE.md Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Ismo Vuorinen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

286
README.md Normal file
View File

@@ -0,0 +1,286 @@
# phpenv.fish
A fast, feature-rich PHP version manager for Fish Shell that acts like goenv or nvm.
## Features
- **Fast version detection** (100-1000x faster than `brew list`)
- **Dynamic version resolution** from [shivammathur/setup-php](https://github.com/shivammathur/setup-php)
- **Multiple version sources**: `.php-version`, `.tool-version`, `composer.json`
- **Auto-installation** of missing PHP versions
- **Extension management** with availability checking
- **Composer.json integration** with full semver support
- **Auto-switching** between versions (configurable)
- **Fisher package manager** support
- **Rich completions** with descriptions
## Installation
### Using Fisher (recommended)
```bash
fisher install ivuorinen/phpenv.fish
```
### Manual Installation
1. Copy files to your fish configuration:
```bash
# Functions
curl -L https://raw.githubusercontent.com/ivuorinen/phpenv.fish/main/functions/phpenv.fish > ~/.config/fish/functions/phpenv.fish
# Completions
curl -L https://raw.githubusercontent.com/ivuorinen/phpenv.fish/main/completions/phpenv.fish > ~/.config/fish/completions/phpenv.fish
# Configuration
curl -L https://raw.githubusercontent.com/ivuorinen/phpenv.fish/main/conf.d/phpenv.fish > ~/.config/fish/conf.d/phpenv.fish
```
2. Install dependencies:
```bash
brew install jq
```
3. Add Homebrew taps:
```bash
brew tap shivammathur/php
brew tap shivammathur/extensions
```
## Quick Start
```bash
# Show available versions
phpenv versions
# Install PHP versions
phpenv install 8.3
phpenv install 8.1
# Set global default
phpenv global 8.3
# Set project-specific version
phpenv local 8.1
# Install extensions
phpenv extensions install xdebug
phpenv extensions install redis
# Configure behavior
phpenv config set auto-switch false # Disable auto-switching
phpenv config set auto-install true # Enable auto-installation
# Check installation
phpenv doctor
```
## Commands
### Version Management
- `phpenv install <version>` - Install PHP version
- `phpenv uninstall <version>` - Uninstall PHP version
- `phpenv use <version>` - Use version for current shell
- `phpenv local <version>` - Set version for current project
- `phpenv global <version>` - Set global default version
- `phpenv list` - List installed versions
- `phpenv current` - Show current version
- `phpenv versions` - Show all available versions
### Extension Management
- `phpenv extensions install <ext>` - Install extension for current PHP
- `phpenv extensions uninstall <ext>` - Uninstall extension
- `phpenv extensions list` - List installed extensions
- `phpenv extensions available` - Show available extensions
### Configuration
- `phpenv config get <key>` - Get configuration value
- `phpenv config set <key> <value>` - Set configuration value
- `phpenv config list` - List all configuration
### Utilities
- `phpenv which [binary]` - Show path to PHP binary
- `phpenv doctor` - Check installation health
- `phpenv help` - Show help
## Version Detection
phpenv automatically detects PHP versions from multiple sources (in priority order):
1. **`.php-version`** - Project-specific version file
2. **`.tool-version`** - Tool version file (parses `v8.4` as `8.4`)
3. **`composer.json`** - Both `config.platform.php` and `require.php` with semver support
4. **Global version** - Fish universal variable
5. **System PHP** - Fallback to system installation
### Composer.json Support
Supports all semver constraints:
- `^8.1` → Uses PHP 8.3 (latest 8.x)
- `~8.2.0` → Uses PHP 8.2
- `>=8.0` → Uses PHP 8.3
- `8.1.*` → Uses PHP 8.1
Checks both locations:
```json
{
"require": {
"php": "^8.1"
},
"config": {
"platform": {
"php": "8.2.0"
}
}
}
```
## Configuration
### Configuration Keys
- `auto-install` - Auto-install missing versions (default: false)
- `auto-install-extensions` - Install extensions with new PHP versions (default: false)
- `auto-switch` - Auto-switch versions when changing directories (default: true)
- `default-extensions` - Space-separated list of default extensions (default: "opcache")
- `global-version` - Global PHP version
### Configuration Files
phpenv checks these locations (in order):
1. `~/.config/fish/conf.d/phpenv.fish` (preferred)
2. `~/.config/phpenv/config`
3. `~/.phpenv.fish`
### Examples
```bash
# Enable auto-installation
phpenv config set auto-install true
# Disable auto-switching
phpenv config set auto-switch false
# Set default extensions
phpenv config set default-extensions "opcache xdebug redis"
# Enable auto-extension installation
phpenv config set auto-install-extensions true
```
## Supported PHP Versions
Uses [shivammathur/homebrew-php](https://github.com/shivammathur/homebrew-php) with dynamic version detection:
**Version Aliases:**
- `latest` - Latest stable PHP version
- `nightly` - Development version
- `8.x` - Latest PHP 8.x version
- `7.x` - Latest PHP 7.x version
- `5.x` - Latest PHP 5.x version
**Available Versions:** 5.6, 7.0-7.4, 8.0-8.5
## Supported Extensions
Uses [shivammathur/homebrew-extensions](https://github.com/shivammathur/homebrew-extensions):
- xdebug, redis, imagick, mongodb, memcached
- pcov, ast, grpc, protobuf, yaml
- And many more...
## Performance
- **Directory checks**: ~1-5ms (vs ~1000ms for `brew list`)
- **Bulk version detection**: ~10ms for all versions
- **No Ruby overhead** or git operations
- **Efficient caching** and lazy loading
## Auto-switching
phpenv automatically switches PHP versions when you change directories if a version file is detected in the project.
## Fisher Integration
Works seamlessly with Fisher package manager:
```bash
# Install
fisher install ivuorinen/phpenv.fish
# Update
fisher update ivuorinen/phpenv.fish
# Uninstall
fisher remove ivuorinen/phpenv.fish
```
## Troubleshooting
### Check Installation
```bash
phpenv doctor
```
### Common Issues
#### jq not found
```bash
brew install jq
```
#### PHP version not found
```bash
# Add taps manually
brew tap shivammathur/php
brew tap shivammathur/extensions
# Install specific version
phpenv install 8.3
```
#### Slow performance
- phpenv is designed to be fast by avoiding `brew list`
- If performance issues persist, check your filesystem or try `phpenv doctor`
### Debug Information
```bash
# Check current detection
phpenv current
# Check which binary is used
phpenv which php
# List all configuration
phpenv config list
```
## Contributing
Contributions welcome! Please:
1. Follow fish shell best practices
2. Add tests for new functionality
3. Update documentation
4. Maintain performance optimizations
## License
MIT License - see LICENSE file for details.
## Related Projects
- [shivammathur/homebrew-php](https://github.com/shivammathur/homebrew-php)
- [shivammathur/homebrew-extensions](https://github.com/shivammathur/homebrew-extensions)
- [jorgebucaran/fisher](https://github.com/jorgebucaran/fisher)

123
completions/phpenv.fish Normal file
View File

@@ -0,0 +1,123 @@
# Completions for phpenv command
# Place in ~/.config/fish/completions/phpenv.fish
# Complete main commands
complete -c phpenv -f -n "__fish_use_subcommand" -a "install" -d "Install a PHP version"
complete -c phpenv -f -n "__fish_use_subcommand" -a "uninstall" -d "Uninstall a PHP version"
complete -c phpenv -f -n "__fish_use_subcommand" -a "use" -d "Use PHP version for current shell"
complete -c phpenv -f -n "__fish_use_subcommand" -a "local" -d "Set PHP version for current project"
complete -c phpenv -f -n "__fish_use_subcommand" -a "global" -d "Set global PHP version"
complete -c phpenv -f -n "__fish_use_subcommand" -a "list" -d "List installed PHP versions"
complete -c phpenv -f -n "__fish_use_subcommand" -a "ls" -d "List installed PHP versions"
complete -c phpenv -f -n "__fish_use_subcommand" -a "current" -d "Show current PHP version"
complete -c phpenv -f -n "__fish_use_subcommand" -a "which" -d "Show path to PHP binary"
complete -c phpenv -f -n "__fish_use_subcommand" -a "versions" -d "Show all available versions"
complete -c phpenv -f -n "__fish_use_subcommand" -a "doctor" -d "Check phpenv installation"
complete -c phpenv -f -n "__fish_use_subcommand" -a "config" -d "Manage configuration"
complete -c phpenv -f -n "__fish_use_subcommand" -a "extensions" -d "Manage PHP extensions"
complete -c phpenv -f -n "__fish_use_subcommand" -a "ext" -d "Manage PHP extensions"
complete -c phpenv -f -n "__fish_use_subcommand" -a "help" -d "Show help"
# Helper functions for completions
function __phpenv_complete_installed_versions
phpenv list 2>/dev/null | sed 's/^[* ]*//'
end
function __phpenv_complete_available_versions
# Try to get dynamic versions first
if command -q curl -a command -q jq; and functions -q __phpenv_parse_version_field
echo "latest"
echo "nightly"
echo "5.x"
echo "7.x"
echo "8.x"
__phpenv_parse_version_field "latest" "8.4"
__phpenv_parse_version_field "nightly" "8.5"
__phpenv_parse_version_field "5.x" "5.6"
__phpenv_parse_version_field "7.x" "7.4"
__phpenv_parse_version_field "8.x" "8.4"
end
# Fallback to common versions
printf "5.6\n7.0\n7.1\n7.2\n7.3\n7.4\n8.0\n8.1\n8.2\n8.3\n8.4\n8.5\n"
end
function __phpenv_complete_config_keys
printf "global-version\nauto-install\nauto-install-extensions\nauto-switch\ndefault-extensions\n"
end
function __phpenv_complete_extensions
printf "xdebug\nredis\nimagick\nmongodb\nmemcached\npcov\nast\ngrpc\n"
printf "protobuf\nyaml\nzip\ncurl\ngd\nintl\nmbstring\nmysql\nopcache\npdo\nsockets\nxml\n"
end
function __phpenv_complete_binaries
printf "php\nphp-config\nphpize\ncomposer\npecl\npear\n"
end
# Complete versions for install command
complete -c phpenv -f -n "__fish_seen_subcommand_from install" \
-a "(__phpenv_complete_available_versions)" -d "PHP version"
# Complete installed versions for uninstall, use commands
complete -c phpenv -f -n "__fish_seen_subcommand_from uninstall use local global" \
-a "(__phpenv_complete_installed_versions)" -d "Installed PHP version"
# Add system option for use command
complete -c phpenv -f -n "__fish_seen_subcommand_from use" -a "system" -d "Use system PHP"
# Complete binaries for which command
complete -c phpenv -f -n "__fish_seen_subcommand_from which" -a "(__phpenv_complete_binaries)" -d "PHP binary"
# Complete config subcommands
complete -c phpenv -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set list" \
-a "get" -d "Get configuration value"
complete -c phpenv -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set list" \
-a "set" -d "Set configuration value"
complete -c phpenv -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set list" \
-a "list" -d "List all configuration"
# Complete config keys
complete -c phpenv -f -n "__fish_seen_subcommand_from config; and __fish_seen_subcommand_from get set" \
-a "(__phpenv_complete_config_keys)" -d "Configuration key"
# Complete config values for boolean settings
complete -c phpenv -f \
-n "__fish_seen_subcommand_from config; and __fish_seen_subcommand_from set" \
-n "contains -- (commandline -opc)[-1] auto-install auto-install-extensions auto-switch" \
-a "true false" -d "Boolean value"
# Complete extensions subcommands
complete -c phpenv -f \
-n "__fish_seen_subcommand_from extensions ext" \
-n "not __fish_seen_subcommand_from install uninstall remove list ls available" \
-a "install" -d "Install PHP extension"
complete -c phpenv -f \
-n "__fish_seen_subcommand_from extensions ext" \
-n "not __fish_seen_subcommand_from install uninstall remove list ls available" \
-a "uninstall" -d "Uninstall PHP extension"
complete -c phpenv -f \
-n "__fish_seen_subcommand_from extensions ext" \
-n "not __fish_seen_subcommand_from install uninstall remove list ls available" \
-a "remove" -d "Remove PHP extension"
complete -c phpenv -f \
-n "__fish_seen_subcommand_from extensions ext" \
-n "not __fish_seen_subcommand_from install uninstall remove list ls available" \
-a "list" -d "List installed extensions"
complete -c phpenv -f \
-n "__fish_seen_subcommand_from extensions ext" \
-n "not __fish_seen_subcommand_from install uninstall remove list ls available" \
-a "ls" -d "List installed extensions"
complete -c phpenv -f \
-n "__fish_seen_subcommand_from extensions ext" \
-n "not __fish_seen_subcommand_from install uninstall remove list ls available" \
-a "available" -d "Show available extensions"
# Complete extension names
complete -c phpenv -f \
-n "__fish_seen_subcommand_from extensions ext; and __fish_seen_subcommand_from install" \
-a "(__phpenv_complete_extensions)" -d "PHP extension"
# Complete help options
complete -c phpenv -f -s h -l help -d "Show help"

38
conf.d/phpenv.fish Normal file
View File

@@ -0,0 +1,38 @@
# phpenv configuration file
# Place in ~/.config/fish/conf.d/phpenv.fish
# Set default configuration using session variables for most settings
# Only PHPENV_GLOBAL_VERSION needs to persist across shells
if not set -q PHPENV_AUTO_INSTALL
set -g PHPENV_AUTO_INSTALL false
end
if not set -q PHPENV_AUTO_INSTALL_EXTENSIONS
set -g PHPENV_AUTO_INSTALL_EXTENSIONS false
end
if not set -q PHPENV_AUTO_SWITCH
set -g PHPENV_AUTO_SWITCH true
end
if not set -q PHPENV_DEFAULT_EXTENSIONS
set -g PHPENV_DEFAULT_EXTENSIONS "opcache"
end
# Initialize PATH on shell startup if global version is set (less aggressive)
if test -n "$PHPENV_GLOBAL_VERSION"; and not set -q PHPENV_INITIALIZED
if functions -q __phpenv_is_version_installed __phpenv_set_php_path
if __phpenv_is_version_installed "$PHPENV_GLOBAL_VERSION" 2>/dev/null
# Only set PATH if no project-specific version is detected
if not __phpenv_find_version_file .php-version >/dev/null 2>&1
if not __phpenv_find_version_file .tool-version >/dev/null 2>&1
if not test -f composer.json
__phpenv_set_php_path "$PHPENV_GLOBAL_VERSION" 2>/dev/null
end
end
end
end
end
set -g PHPENV_INITIALIZED true
end

85
fish_plugins Normal file
View File

@@ -0,0 +1,85 @@
# Fisher package definition for phpenv.fish
# This file enables installation via: fisher install ivuorinen/phpenv.fish
function __phpenv_install --on-event phpenv_install
echo "Installing phpenv.fish..."
# Check for required dependencies
if not command -q jq
echo "Installing jq dependency..."
if command -q brew
brew install jq
else
echo "Error: Homebrew is required to install jq dependency" >&2
echo "Install Homebrew first: https://brew.sh" >&2
return 1
end
end
# Add required Homebrew taps
if command -q brew; and functions -q __phpenv_ensure_taps
echo "Adding Homebrew taps..."
__phpenv_ensure_taps
end
# Set default configuration if not already set (use session variables)
if not set -q PHPENV_AUTO_INSTALL
set -g PHPENV_AUTO_INSTALL false
echo "Set PHPENV_AUTO_INSTALL=false (change with: phpenv config set auto-install true)"
end
if not set -q PHPENV_AUTO_INSTALL_EXTENSIONS
set -g PHPENV_AUTO_INSTALL_EXTENSIONS false
echo "Set PHPENV_AUTO_INSTALL_EXTENSIONS=false"
end
if not set -q PHPENV_AUTO_SWITCH
set -g PHPENV_AUTO_SWITCH true
echo "Set PHPENV_AUTO_SWITCH=true (automatic version switching enabled)"
end
if not set -q PHPENV_DEFAULT_EXTENSIONS
set -g PHPENV_DEFAULT_EXTENSIONS "opcache"
echo "Set default extensions: opcache"
end
echo "phpenv.fish installed successfully!"
echo ""
echo "Quick start:"
echo " phpenv versions # Show available versions"
echo " phpenv install 8.3 # Install PHP 8.3"
echo " phpenv global 8.3 # Set as global default"
echo " phpenv help # Show all commands"
echo ""
echo "Run 'phpenv doctor' to check your installation."
end
function __phpenv_update --on-event phpenv_update
echo "Updating phpenv.fish..."
__phpenv_install
end
function __phpenv_uninstall --on-event phpenv_uninstall
echo "Uninstalling phpenv.fish..."
# Remove all phpenv variables (both session and universal)
set -e PHPENV_AUTO_INSTALL
set -e PHPENV_AUTO_INSTALL_EXTENSIONS
set -e PHPENV_AUTO_SWITCH
set -e PHPENV_DEFAULT_EXTENSIONS
set -e PHPENV_GLOBAL_VERSION
set -e PHPENV_ORIGINAL_PATH
set -e PHPENV_CURRENT_VERSION
set -e PHPENV_CURRENT_PATH
set -e PHPENV_LAST_SWITCH_TIME
set -e PHPENV_VERSION_OVERRIDE
set -e PHPENV_INITIALIZED
# Remove cache variables
set -e __phpenv_version_cache
set -e __phpenv_version_cache_time
set -e __phpenv_cellar_cache
echo "phpenv.fish configuration removed."
echo "Note: Installed PHP versions are preserved."
end

1078
functions/phpenv.fish Normal file

File diff suppressed because it is too large Load Diff