diff --git a/.editorconfig b/.editorconfig index e77edde..9cf0d48 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,3 +11,7 @@ charset = utf-8 trim_trailing_whitespace = true max_line_length = 120 +# Markdown files +[*.md] +max_line_length = 200 + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c5e62dd..37d9738 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,61 +3,95 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - - id: requirements-txt-fixer - - id: detect-private-key - - id: trailing-whitespace - args: [--markdown-linebreak-ext=md] + - id: check-added-large-files + - id: check-ast + - id: check-builtin-literals - id: check-case-conflict - - id: check-merge-conflict - id: check-executables-have-shebangs + - id: check-json + - id: check-merge-conflict - id: check-shebang-scripts-are-executable - id: check-symlinks - id: check-toml - id: check-xml - id: check-yaml args: [--allow-multiple-documents] + - id: debug-statements + - id: detect-private-key - id: end-of-file-fixer - id: mixed-line-ending args: [--fix=auto] - id: pretty-format-json args: [--autofix, --no-sort-keys] + - id: requirements-txt-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + # Security scanning + - repo: https://github.com/Yelp/detect-secrets + rev: v1.5.0 + hooks: + - id: detect-secrets + args: ['--baseline', '.secrets.baseline'] + + - repo: https://github.com/gitleaks/gitleaks + rev: v8.28.0 + hooks: + - id: gitleaks + + # Markdown linting - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.45.0 hooks: - id: markdownlint args: [-c, .markdownlint.json, --fix] + # EditorConfig linting + - repo: https://github.com/editorconfig-checker/editorconfig-checker.python + rev: 3.0.3 + hooks: + - id: editorconfig-checker + alias: ec + + # YAML linting - repo: https://github.com/adrienverge/yamllint rev: v1.37.1 hooks: - id: yamllint + # Shell formatting - repo: https://github.com/scop/pre-commit-shfmt - rev: v3.11.0-1 + rev: v3.12.0-2 hooks: - id: shfmt + args: [-w, -s, -i, '2'] + # Shell linting - repo: https://github.com/koalaman/shellcheck-precommit rev: v0.11.0 hooks: - id: shellcheck args: ['--severity=warning'] + # GitHub Actions linting - repo: https://github.com/rhysd/actionlint rev: v1.7.7 hooks: - id: actionlint args: ['-shellcheck='] + # Renovate config validation - repo: https://github.com/renovatebot/pre-commit-hooks rev: 41.132.5 hooks: - id: renovate-config-validator + # Security scanning - repo: https://github.com/bridgecrewio/checkov.git rev: '3.2.479' hooks: - id: checkov args: - '--quiet' + - '--framework' + - 'github_actions,yaml' diff --git a/.secrets.baseline b/.secrets.baseline new file mode 100644 index 0000000..3cf2d56 --- /dev/null +++ b/.secrets.baseline @@ -0,0 +1,113 @@ +{ + "version": "1.5.0", + "plugins_used": [ + { + "name": "ArtifactoryDetector" + }, + { + "name": "AWSKeyDetector" + }, + { + "name": "AzureStorageKeyDetector" + }, + { + "name": "Base64HighEntropyString", + "limit": 4.5 + }, + { + "name": "BasicAuthDetector" + }, + { + "name": "CloudantDetector" + }, + { + "name": "DiscordBotTokenDetector" + }, + { + "name": "GitHubTokenDetector" + }, + { + "name": "HexHighEntropyString", + "limit": 3.0 + }, + { + "name": "IbmCloudIamDetector" + }, + { + "name": "IbmCosHmacDetector" + }, + { + "name": "JwtTokenDetector" + }, + { + "name": "KeywordDetector", + "keyword_exclude": "" + }, + { + "name": "MailchimpDetector" + }, + { + "name": "NpmDetector" + }, + { + "name": "PrivateKeyDetector" + }, + { + "name": "SendGridDetector" + }, + { + "name": "SlackDetector" + }, + { + "name": "SoftlayerDetector" + }, + { + "name": "SquareOAuthDetector" + }, + { + "name": "StripeDetector" + }, + { + "name": "TwilioKeyDetector" + } + ], + "filters_used": [ + { + "path": "detect_secrets.filters.allowlist.is_line_allowlisted" + }, + { + "path": "detect_secrets.filters.common.is_baseline_file", + "filename": ".secrets.baseline" + }, + { + "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", + "min_level": 2 + }, + { + "path": "detect_secrets.filters.heuristic.is_likely_id_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_lock_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_potential_uuid" + }, + { + "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" + }, + { + "path": "detect_secrets.filters.heuristic.is_sequential_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_swagger_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_templated_secret" + } + ], + "results": {}, + "generated_at": "2024-09-26T20:30:00Z" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a870ce8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,313 @@ +# Contributing to phpenv.fish + +Thank you for your interest in contributing to phpenv.fish! This document provides guidelines and instructions for contributors. + +## Table of Contents + +- [Development Setup](#development-setup) +- [Code Architecture](#code-architecture) +- [Making Changes](#making-changes) +- [Testing](#testing) +- [Code Quality](#code-quality) +- [Performance Guidelines](#performance-guidelines) +- [Submitting Changes](#submitting-changes) + +## Development Setup + +### Prerequisites + +- Fish Shell 3.0+ +- Homebrew (for PHP version management) +- jq (JSON processor) +- Git + +### Installation for Development + +1. Fork and clone the repository: + + ```bash + git clone https://github.com/YOUR-USERNAME/phpenv.fish.git + cd phpenv.fish + ``` + +2. Link the development version to your Fish config: + + ```bash + # Create backup of existing installation if any + mv ~/.config/fish/functions/phpenv.fish ~/.config/fish/functions/phpenv.fish.backup 2>/dev/null || true + + # Link development version + 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 + ``` + +3. Install dependencies: + + ```bash + brew install jq + brew tap shivammathur/php + brew tap shivammathur/extensions + ``` + +4. Set up pre-commit hooks: + + ```bash + pip install pre-commit + pre-commit install + ``` + +### Testing Changes + +Since this is a Fish shell plugin, test changes by: + +```bash +# Reload the function after changes +source functions/phpenv.fish + +# Test basic commands +phpenv help +phpenv versions +phpenv current +phpenv doctor + +# Test specific functionality +phpenv install 8.3 # if not already installed +phpenv use 8.3 +phpenv list +``` + +## 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 + +## Making Changes + +### Adding a New Command + +1. Add case in main `phpenv` function switch statement +2. Implement `__phpenv_` function +3. Add completions in `completions/phpenv.fish` +4. Update help text in `__phpenv_help` + +Example: + +```fish +# In main phpenv function +case mynewcommand + __phpenv_mynewcommand $phpenv_args + +# New function implementation +function __phpenv_mynewcommand + # Implementation here +end +``` + +### 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@` +- Extensions: `shivammathur/extensions/@` +- Check formula existence before operations + +## Testing + +### Manual Testing + +Test all major functionality: + +```bash +# Version management +phpenv install 8.3 +phpenv use 8.3 +phpenv local 8.2 +phpenv global 8.3 + +# Extension management +phpenv extensions install xdebug +phpenv extensions list + +# Configuration +phpenv config set auto-switch true +phpenv config list + +# Diagnostics +phpenv doctor +phpenv current +phpenv which php +``` + +### Edge Cases + +Test edge cases: + +- Missing jq dependency +- Network connectivity issues +- Invalid version files +- Corrupt installations +- Missing Homebrew taps + +## Code Quality + +### Pre-commit Hooks + +The repository uses several pre-commit hooks for code quality: + +- **Security**: `detect-secrets`, `gitleaks`, `checkov` +- **Shell**: `shellcheck`, `shfmt` +- **Format**: `markdownlint`, `yamllint` +- **General**: File format validation, trailing whitespace removal + +Run hooks manually: + +```bash +pre-commit run --all-files +``` + +### Code Style Requirements + +- Maximum line length: 120 characters (enforced by `.editorconfig`) +- Use LF line endings +- UTF-8 encoding +- Trim trailing whitespace +- Insert final newline + +### Fish Shell Best Practices + +- Use local variables (`set -l`) for function scope +- Use session variables (`set -g`) for per-shell configuration +- Use universal variables (`set -U`) only for persistent settings +- Prefix all internal functions with `__phpenv_` +- Use descriptive variable names with `phpenv_` prefix +- Handle errors gracefully with proper return codes + +## Performance Guidelines + +### Optimization Principles + +1. **Cache expensive operations** (API calls, filesystem checks) +2. **Unify repeated patterns** into helper functions +3. **Use session variables** instead of universal where possible +4. **Minimize network requests** and subprocess calls + +### Caching System + +The codebase uses intelligent caching: + +- `__phpenv_version_cache`: 5-minute cache for API version data +- `__phpenv_cellar_cache`: Permanent cache for Homebrew Cellar path + +### 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 + +### Helper Functions + +Use unified helper functions to avoid code duplication: + +- `__phpenv_parse_version_field`: Single function for all jq parsing +- `__phpenv_ensure_taps`: Unified Homebrew tap management +- `__phpenv_get_tap_formulas`: Shared formula listing logic + +## Submitting Changes + +### Pull Request Process + +1. **Create a branch** from `main`: + + ```bash + git checkout -b feature/my-improvement + ``` + +2. **Make focused changes**: + - Keep changes small and focused + - Follow the existing code style + - Add tests if applicable + +3. **Run quality checks**: + + ```bash + pre-commit run --all-files + shellcheck functions/phpenv.fish + ``` + +4. **Test thoroughly**: + - Test all affected functionality + - Test edge cases + - Test on different environments if possible + +5. **Commit with clear messages**: + + ```bash + git commit -m "feat: add new command for X functionality" + ``` + +6. **Submit pull request**: + - Provide clear description of changes + - Reference any related issues + - Include testing instructions + +### Commit Message Format + +Follow conventional commits: + +- `feat:` for new features +- `fix:` for bug fixes +- `docs:` for documentation changes +- `perf:` for performance improvements +- `refactor:` for code refactoring +- `test:` for adding tests + +### Code Review + +All changes require code review. The maintainer will: + +- Review for code quality and style +- Test functionality +- Check performance impact +- Verify documentation updates + +## Getting Help + +- **Issues**: Open a GitHub issue for bugs or feature requests +- **Discussions**: Use GitHub Discussions for questions +- **Documentation**: Check the README and CLAUDE.md files + +Thank you for contributing to phpenv.fish! 🐟 diff --git a/README.md b/README.md index 485fb61..925ca5f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ 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 @@ -37,11 +38,13 @@ fisher install ivuorinen/phpenv.fish ``` 2. Install dependencies: + ```bash brew install jq ``` 3. Add Homebrew taps: + ```bash brew tap shivammathur/php brew tap shivammathur/extensions @@ -120,12 +123,14 @@ phpenv automatically detects PHP versions from multiple sources (in priority ord ### 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": { @@ -178,6 +183,7 @@ phpenv config set auto-install-extensions true 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 @@ -267,12 +273,50 @@ phpenv config list ## Contributing -Contributions welcome! Please: +Contributions welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines. + +**Quick Guidelines:** 1. Follow fish shell best practices 2. Add tests for new functionality 3. Update documentation 4. Maintain performance optimizations +5. Run pre-commit hooks before submitting + +**Development Setup:** + +```bash +# Link 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 + +# Install pre-commit hooks +pip install pre-commit +pre-commit install +``` + +## Performance Optimizations + +phpenv.fish is designed for speed and efficiency: + +### Caching System + +- **API Data Caching**: Version information cached for 5 minutes +- **Homebrew Path Caching**: Cellar path permanently cached +- **Smart Debouncing**: Auto-switching limited to prevent excessive PATH changes + +### Unified Helper Functions + +- **Consolidated jq Parsing**: Single function eliminates repeated API calls +- **Unified Tap Management**: Shared logic for Homebrew tap operations +- **Optimized Formula Listing**: Cached and shared across operations + +### Performance Metrics + +- **100-1000x faster** than `brew list` for version detection +- **Direct directory checks** instead of subprocess calls +- **Minimal network requests** with intelligent caching ## License @@ -283,4 +327,3 @@ MIT License - see LICENSE file for details. - [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) - diff --git a/functions/phpenv.fish b/functions/phpenv.fish index 40a5d2f..9d296f6 100644 --- a/functions/phpenv.fish +++ b/functions/phpenv.fish @@ -236,12 +236,15 @@ function __phpenv_ensure_taps return 1 end - # Add taps if not already added - if not brew tap | grep -q shivammathur/php 2>/dev/null - brew tap shivammathur/php 2>/dev/null - end - if not brew tap | grep -q shivammathur/extensions 2>/dev/null - brew tap shivammathur/extensions 2>/dev/null + # Check and add required taps only if missing + set -l required_taps "shivammathur/php" "shivammathur/extensions" + for tap in $required_taps + if not brew tap | grep -q $tap 2>/dev/null + if not brew tap $tap 2>/dev/null + echo "Warning: Failed to add tap $tap" >&2 + return 1 + end + end end end @@ -569,8 +572,7 @@ function __phpenv_get_tap_versions return end - set -l phpenv_formulas (brew tap-info shivammathur/php --json 2>/dev/null | \ - jq -r '.[]|(.formula_names[]?)' 2>/dev/null) + set -l phpenv_formulas (__phpenv_get_tap_formulas "shivammathur/php") if test -z "$phpenv_formulas" return @@ -672,39 +674,51 @@ function __phpenv_config -a phpenv_action phpenv_key phpenv_value end end +# Helper function to get environment variable value +function __phpenv_get_env_var -a key + switch $key + case global-version + echo $PHPENV_GLOBAL_VERSION + case auto-install + echo $PHPENV_AUTO_INSTALL + case auto-install-extensions + echo $PHPENV_AUTO_INSTALL_EXTENSIONS + case auto-switch + echo $PHPENV_AUTO_SWITCH + case default-extensions + echo $PHPENV_DEFAULT_EXTENSIONS + end +end + +# Helper function to map config key to env var name +function __phpenv_env_var_name -a key + switch $key + case global-version + echo PHPENV_GLOBAL_VERSION + case auto-install + echo PHPENV_AUTO_INSTALL + case auto-install-extensions + echo PHPENV_AUTO_INSTALL_EXTENSIONS + case auto-switch + echo PHPENV_AUTO_SWITCH + case default-extensions + echo PHPENV_DEFAULT_EXTENSIONS + case '*' + echo "" + end +end + function __phpenv_config_get -a phpenv_key + set -l phpenv_env_var (__phpenv_env_var_name $phpenv_key) set -l phpenv_value set -l phpenv_source - switch $phpenv_key - case global-version - if test -n "$PHPENV_GLOBAL_VERSION" - set phpenv_value $PHPENV_GLOBAL_VERSION - set phpenv_source "fish universal variable" - end - case auto-install - if test -n "$PHPENV_AUTO_INSTALL" - set phpenv_value $PHPENV_AUTO_INSTALL - set phpenv_source "fish universal variable" - end - case auto-install-extensions - if test -n "$PHPENV_AUTO_INSTALL_EXTENSIONS" - set phpenv_value $PHPENV_AUTO_INSTALL_EXTENSIONS - set phpenv_source "fish universal variable" - end - case auto-switch - if test -n "$PHPENV_AUTO_SWITCH" - set phpenv_value $PHPENV_AUTO_SWITCH - set phpenv_source "fish universal variable" - end - case default-extensions - if test -n "$PHPENV_DEFAULT_EXTENSIONS" - set phpenv_value $PHPENV_DEFAULT_EXTENSIONS - set phpenv_source "fish universal variable" - end - end - - if test -z "$phpenv_value" + # Check if environment variable is set + if test -n "$phpenv_env_var" -a (set -q $phpenv_env_var) + set phpenv_value (eval echo \$$phpenv_env_var) + set phpenv_source "fish universal variable" + else + # Check config files if environment variable is unset for phpenv_config_file in ~/.config/fish/conf.d/phpenv.fish ~/.config/phpenv/config ~/.phpenv.fish if test -f $phpenv_config_file set -l phpenv_file_value (grep "^$phpenv_key=" $phpenv_config_file | cut -d= -f2- | head -1) @@ -867,15 +881,20 @@ function __phpenv_extensions_uninstall -a phpenv_extension end end -function __phpenv_get_available_extensions +# Unified helper for getting tap formulas +function __phpenv_get_tap_formulas -a tap_name if not command -q brew return 1 end - brew tap-info shivammathur/extensions --json 2>/dev/null | \ + brew tap-info $tap_name --json 2>/dev/null | \ jq -r '.[]|(.formula_names[]?)' 2>/dev/null end +function __phpenv_get_available_extensions + __phpenv_get_tap_formulas "shivammathur/extensions" +end + function __phpenv_extension_available -a phpenv_extension phpenv_version set -l phpenv_available_extensions (__phpenv_get_available_extensions)