diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index f7492c5..7f992b4 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -3,11 +3,18 @@ name: run-tests on: push: paths: - - '**.php' - - '.github/workflows/run-tests.yml' - - 'phpunit.xml.dist' - - 'composer.json' - - 'composer.lock' + - "**.php" + - ".github/workflows/run-tests.yml" + - "phpunit.xml.dist" + - "composer.json" + - "composer.lock" + pull_request: + paths: + - "**.php" + - ".github/workflows/run-tests.yml" + - "phpunit.xml.dist" + - "composer.json" + - "composer.lock" jobs: test: @@ -17,13 +24,14 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest, windows-latest] - php: [8.3, 8.2, 8.1] - laravel: [10.*] + php: [8.3, 8.2] + laravel: [11.*, 10.*] stability: [prefer-lowest, prefer-stable] include: + - laravel: 11.* + testbench: 9.* - laravel: 10.* testbench: 8.* - carbon: ^2.63 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} @@ -45,7 +53,7 @@ jobs: - name: Install dependencies run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:${{ matrix.carbon }}" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update composer update --${{ matrix.stability }} --prefer-dist --no-interaction - name: List Installed Dependencies @@ -53,3 +61,45 @@ jobs: - name: Execute tests run: vendor/bin/pest --ci + + static-analysis: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv + coverage: none + + - name: Install dependencies + run: composer update --prefer-stable --prefer-dist --no-interaction + + - name: Run PHPStan + run: vendor/bin/phpstan analyse --error-format=github + + code-style: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + extensions: dom, curl, libxml, mbstring, zip + coverage: none + + - name: Install dependencies + run: composer update --prefer-stable --prefer-dist --no-interaction + + - name: Check code style + run: vendor/bin/pint --test diff --git a/.gitignore b/.gitignore index 28b0378..e860715 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ .idea .phpunit.cache composer.lock -coverage -docs +coverage/ +docs/ phpunit.xml +phpunit.xml.dist phpstan.neon testbench.yaml -vendor -node_modules +vendor/ +node_modules/ +build/report.junit.xml +coverage/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 842b2af..05974fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,42 @@ All notable changes to `versioning` will be documented in this file. +## v3.0.0 - 2025-12-01 + +### Major Updates 🎉 + +* **BREAKING**: Dropped PHP 8.1 support, now requires PHP 8.2+ +* **BREAKING**: Updated Laravel support to v10 and v11 +* Added comprehensive configuration file support +* Added built-in caching mechanism for performance +* Added multiple version format options (tag, full, commit, tag-commit) +* Added proper error handling and security improvements +* Added extensive test coverage with Pest +* Added PHPStan for static analysis (level 8) +* Added additional Blade directives (@app_version_tag, @app_version_full, @app_version_commit) +* Added cache clearing functionality +* Updated CI/CD workflow with separate jobs for tests, static analysis, and code style +* Improved documentation with examples and troubleshooting +* Security: Proper command sanitization using escapeshellarg +* Security: Added fallback version support for non-git environments + +### New Features + +* `Versioning::full()` - Get full git describe output +* `Versioning::commit()` - Get commit hash +* `Versioning::tagWithCommit()` - Get tag with commit +* `Versioning::clearCache()` - Clear version cache +* Configuration file with extensive options +* Environment variable support for fallback version +* Configurable version prefix (v or no v) + +### Developer Experience + +* Added Larastan for Laravel-specific static analysis +* Updated Pint to latest version +* Added comprehensive test suite +* Improved code quality and maintainability + ## v2.0.3 - 2024-04-18 ### What's Changed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8bd9d19 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,107 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +We accept contributions via Pull Requests on [Github](https://github.com/williamug/versioning). + +## Pull Requests + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. + +- **Create feature branches** - Don't ask us to pull from your main branch. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. + +## Running Tests + +```bash +composer test +``` + +## Running Static Analysis + +```bash +composer analyse +``` + +## Code Style + +We use [Laravel Pint](https://github.com/laravel/pint) for code styling. + +```bash +composer format +``` + +To check code style without fixing: + +```bash +vendor/bin/pint --test +``` + +## Development Setup + +1. Fork the repository +2. Clone your fork: `git clone https://github.com/your-username/versioning.git` +3. Install dependencies: `composer install` +4. Create a branch: `git checkout -b my-new-feature` +5. Make your changes +6. Run tests: `composer test` +7. Run static analysis: `composer analyse` +8. Format code: `composer format` +9. Commit your changes: `git commit -am 'Add some feature'` +10. Push to the branch: `git push origin my-new-feature` +11. Submit a pull request + +## Guidelines + +### Coding Standards + +- Follow PSR-12 coding standards +- Use type hints wherever possible +- Add docblocks for classes and methods +- Keep methods small and focused + +### Testing + +- Write tests for all new features +- Ensure tests are clear and descriptive +- Use Pest syntax for consistency +- Aim for high test coverage + +### Documentation + +- Update README.md if you change functionality +- Add PHPDoc blocks to new methods +- Include usage examples for new features + +### Commit Messages + +- Use clear and meaningful commit messages +- Start with a verb in present tense (Add, Update, Fix, Remove) +- Reference issue numbers when applicable + +Example: +``` +Add cache clearing functionality + +- Implement clearCache() method +- Add tests for cache clearing +- Update documentation + +Fixes #123 +``` + +## Questions? + +If you have any questions about contributing, feel free to: +- Open an issue +- Start a discussion +- Contact the maintainer + +**Happy coding!** diff --git a/FRAMEWORK-INTEGRATION.md b/FRAMEWORK-INTEGRATION.md new file mode 100644 index 0000000..d229126 --- /dev/null +++ b/FRAMEWORK-INTEGRATION.md @@ -0,0 +1,589 @@ +# Framework Integration Guide + +This package works seamlessly with **any PHP framework**. This guide shows you how to integrate it with popular frameworks. + +## Quick Overview + +The package provides three classes for different use cases: + +| Class | Best For | Dependencies | +|-------|----------|--------------| +| `UniversalVersioning` | Any framework with cache | None (auto-detects cache) | +| `StandaloneVersioning` | Vanilla PHP / No cache | None | +| `Versioning` | Laravel projects | Laravel facades | + +## Universal Integration + +The `UniversalVersioning` class auto-detects and works with: +- PSR-6 Cache (CacheItemPoolInterface) +- PSR-16 Simple Cache (CacheInterface) +- Laravel Cache +- Symfony Cache +- CodeIgniter Cache +- Any cache system with `get/set` methods + +### Basic Setup (Any Framework) + +```php +use Williamug\Versioning\UniversalVersioning; + +// Configure once in your bootstrap/config +UniversalVersioning::setRepositoryPath(__DIR__); +UniversalVersioning::setCacheAdapter($yourCacheInstance); +UniversalVersioning::setFallbackVersion('1.0.0'); +UniversalVersioning::setCacheTtl(3600); // 1 hour + +// Use anywhere +echo UniversalVersioning::tag(); // v1.0.0 +``` + +--- + +## Laravel + +### Method 1: Using Built-in Integration + +```php +use Williamug\Versioning\Versioning; + +// In controllers +$version = Versioning::tag(); + +// In Blade templates +@app_version_tag +@app_version_full +@app_version_commit +``` + +### Method 2: Using Universal Class + +```php +use Illuminate\Support\Facades\Cache; +use Williamug\Versioning\UniversalVersioning; + +UniversalVersioning::setRepositoryPath(base_path()); +UniversalVersioning::setCacheAdapter(Cache::getFacadeRoot()); + +$version = UniversalVersioning::tag(); +``` + +**Configuration:** Publish config with `php artisan vendor:publish --tag="versioning-config"` + +--- + +## Symfony + +### Setup + +```php +// src/Service/VersioningService.php +namespace App\Service; + +use Symfony\Contracts\Cache\CacheInterface; +use Williamug\Versioning\UniversalVersioning; + +class VersioningService +{ + public function __construct( + private CacheInterface $cache, + private string $projectDir + ) { + UniversalVersioning::setRepositoryPath($this->projectDir); + UniversalVersioning::setCacheAdapter($this->cache); + UniversalVersioning::setFallbackVersion($_ENV['APP_VERSION'] ?? 'dev'); + } + + public function getVersion(): string + { + return UniversalVersioning::tag(); + } +} +``` + +### Configuration (config/services.yaml) + +```yaml +services: + App\Service\VersioningService: + arguments: + $cache: '@cache.app' + $projectDir: '%kernel.project_dir%' +``` + +### Usage in Controllers + +```php +use App\Service\VersioningService; + +#[Route('/')] +public function index(VersioningService $versioning): Response +{ + return $this->render('index.html.twig', [ + 'version' => $versioning->getVersion(), + ]); +} +``` + +### Usage in Twig + +```twig +{# Register as global in src/Twig/AppExtension.php #} + +``` + +**Full example:** See `examples/symfony-integration.php` + +--- + +## CodeIgniter 4 + +### Setup + +```php +// app/Libraries/Versioning.php +namespace App\Libraries; + +use Williamug\Versioning\UniversalVersioning; + +class Versioning +{ + public function __construct() + { + $cache = \Config\Services::cache(); + + UniversalVersioning::setRepositoryPath(ROOTPATH); + UniversalVersioning::setCacheAdapter($cache); + UniversalVersioning::setFallbackVersion(env('app.version', 'dev')); + } + + public function getVersion(): string + { + return UniversalVersioning::tag(); + } +} +``` + +### Create Helper + +```php +// app/Helpers/version_helper.php +if (!function_exists('get_app_version')) { + function get_app_version(): string + { + $versioning = new \App\Libraries\Versioning(); + return $versioning->getVersion(); + } +} +``` + +### Load in Autoload + +```php +// app/Config/Autoload.php +public $helpers = ['version']; +``` + +### Usage + +```php +// In controllers +$versioning = new \App\Libraries\Versioning(); +$data['version'] = $versioning->getVersion(); + +// In views + +``` + +**Full example:** See `examples/codeigniter-integration.php` + +--- + +## CakePHP 5 + +### Create Component + +```php +// src/Controller/Component/VersioningComponent.php +namespace App\Controller\Component; + +use Cake\Controller\Component; +use Cake\Cache\Cache; +use Williamug\Versioning\UniversalVersioning; + +class VersioningComponent extends Component +{ + public function initialize(array $config): void + { + parent::initialize($config); + + UniversalVersioning::setRepositoryPath(ROOT); + UniversalVersioning::setCacheAdapter(Cache::pool('default')); + } + + public function getVersion(): string + { + return UniversalVersioning::tag(); + } +} +``` + +### Load in AppController + +```php +public function initialize(): void +{ + parent::initialize(); + $this->loadComponent('Versioning'); +} +``` + +### Usage + +```php +// In controllers +$version = $this->Versioning->getVersion(); + +// In views (after creating helper) +Version->tag() ?> +``` + +**Full example:** See `examples/cakephp-integration.php` + +--- + +## Slim Framework 4 + +### Bootstrap Setup + +```php +use Williamug\Versioning\UniversalVersioning; + +// Configure in bootstrap +UniversalVersioning::setRepositoryPath(__DIR__ . '/..'); +UniversalVersioning::setFallbackVersion(getenv('APP_VERSION') ?: 'dev'); + +// Optional: Add PSR-16 cache +UniversalVersioning::setCacheAdapter($container->get('cache')); +``` + +### Usage in Routes + +```php +$app->get('/', function ($request, $response) { + $version = UniversalVersioning::tag(); + $response->getBody()->write("Version: {$version}"); + return $response; +}); + +$app->get('/api/version', function ($request, $response) { + $data = [ + 'version' => UniversalVersioning::tag(), + 'commit' => UniversalVersioning::commit(), + ]; + $response->getBody()->write(json_encode($data)); + return $response->withHeader('Content-Type', 'application/json'); +}); +``` + +### With Twig + +```php +$twig->getEnvironment()->addGlobal('app_version', UniversalVersioning::tag()); +``` + +**Full example:** See `examples/slim-integration.php` + +--- + +## Laminas (Zend Framework) + +### Setup + +```php +// module/Application/src/Service/VersioningService.php +namespace Application\Service; + +use Laminas\Cache\Storage\StorageInterface; +use Williamug\Versioning\UniversalVersioning; + +class VersioningService +{ + public function __construct(private StorageInterface $cache) + { + UniversalVersioning::setRepositoryPath(getcwd()); + UniversalVersioning::setCacheAdapter($this->cache); + } + + public function getVersion(): string + { + return UniversalVersioning::tag(); + } +} +``` + +### Factory + +```php +// module/Application/src/Service/Factory/VersioningServiceFactory.php +namespace Application\Service\Factory; + +use Application\Service\VersioningService; +use Laminas\ServiceManager\Factory\FactoryInterface; +use Psr\Container\ContainerInterface; + +class VersioningServiceFactory implements FactoryInterface +{ + public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null) + { + return new VersioningService($container->get('cache')); + } +} +``` + +### Configure in module.config.php + +```php +'service_manager' => [ + 'factories' => [ + \Application\Service\VersioningService::class => + \Application\Service\Factory\VersioningServiceFactory::class, + ], +], +``` + +--- + +## Yii2 + +### Setup + +```php +// common/components/Versioning.php +namespace common\components; + +use yii\base\Component; +use Williamug\Versioning\UniversalVersioning; + +class Versioning extends Component +{ + public function init() + { + parent::init(); + + UniversalVersioning::setRepositoryPath(\Yii::getAlias('@app')); + UniversalVersioning::setCacheAdapter(\Yii::$app->cache); + UniversalVersioning::setFallbackVersion(\Yii::$app->params['version'] ?? 'dev'); + } + + public function getVersion(): string + { + return UniversalVersioning::tag(); + } + + public function getCommit(): string + { + return UniversalVersioning::commit(); + } +} +``` + +### Configure in config/web.php + +```php +'components' => [ + 'versioning' => [ + 'class' => 'common\components\Versioning', + ], +], +``` + +### Usage + +```php +// In controllers +$version = Yii::$app->versioning->getVersion(); + +// In views +versioning->getVersion() ?> +``` + +--- + +## Phalcon + +### Setup + +```php +// app/library/Versioning.php +use Williamug\Versioning\UniversalVersioning; + +class Versioning +{ + protected $di; + + public function __construct($di) + { + $this->di = $di; + + UniversalVersioning::setRepositoryPath(BASE_PATH); + UniversalVersioning::setCacheAdapter($di->get('cache')); + } + + public function getVersion(): string + { + return UniversalVersioning::tag(); + } +} +``` + +### Register in DI + +```php +$di->setShared('versioning', function () use ($di) { + return new Versioning($di); +}); +``` + +### Usage + +```php +// In controllers +$version = $this->di->get('versioning')->getVersion(); + +// In Volt templates +{{ di.get('versioning').getVersion() }} +``` + +--- + +## Custom Framework / Legacy PHP + +### Without Cache + +```php +use Williamug\Versioning\StandaloneVersioning; + +StandaloneVersioning::setRepositoryPath(__DIR__); +StandaloneVersioning::setFallbackVersion('1.0.0'); + +echo StandaloneVersioning::tag(); +``` + +### With Custom Cache + +```php +use Williamug\Versioning\UniversalVersioning; + +// Your custom cache class +class MyCache { + public function get($key) { /* ... */ } + public function set($key, $value, $ttl) { /* ... */ } +} + +UniversalVersioning::setCacheAdapter(new MyCache()); +echo UniversalVersioning::tag(); +``` + +--- + +## Cache Adapter Requirements + +The `UniversalVersioning` class automatically detects and works with caches that implement: + +### PSR-16 Simple Cache +```php +interface SimpleCacheInterface { + public function get($key, $default = null); + public function set($key, $value, $ttl = null); + public function delete($key); +} +``` + +### PSR-6 Cache +```php +interface CacheItemPoolInterface { + public function getItem($key); + public function save(CacheItemInterface $item); + public function deleteItem($key); +} +``` + +### Basic Cache Interface +Any object with these methods: +```php +public function get($key); +public function set($key, $value, $ttl); +public function delete($key); +``` + +--- + +## Environment Variables + +All frameworks can use environment variables: + +```bash +# .env file +APP_VERSION=1.0.0 +VERSIONING_CACHE_TTL=3600 +``` + +```php +UniversalVersioning::setFallbackVersion(getenv('APP_VERSION') ?: 'dev'); +UniversalVersioning::setCacheTtl((int) getenv('VERSIONING_CACHE_TTL') ?: 3600); +``` + +--- + +## API Response Example (Any Framework) + +```php +header('Content-Type: application/json'); + +echo json_encode([ + 'app' => 'My Application', + 'version' => UniversalVersioning::tag(), + 'build' => UniversalVersioning::commit(), + 'timestamp' => time(), +]); +``` + +--- + +## Troubleshooting + +### Cache Not Working + +```php +// Test if cache is configured +$cache = $yourCacheInstance; +var_dump(method_exists($cache, 'get')); // Should be true +var_dump(method_exists($cache, 'set')); // Should be true +``` + +### Version Not Updating + +```php +// Clear cache after deployment +UniversalVersioning::clearCache(); +``` + +### Git Not Found + +```php +// Set explicit repository path +UniversalVersioning::setRepositoryPath('/absolute/path/to/repo'); + +// Set fallback version +UniversalVersioning::setFallbackVersion('1.0.0'); +``` + +--- + +## Need Help? + +- See working examples in the `examples/` directory +- Check framework-specific documentation +- Open an issue on GitHub + +The package is designed to work with **any PHP framework** out of the box! 🚀 diff --git a/README.md b/README.md index 9e3ab56..ada3475 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,342 @@ # Versioning -[![Latest Version on Packagist](https://img.shields.io/packagist/v/williamug/versioning.svg?style=flat-square)](https://packagist.org/packages/williamug/versioning/stats#major/all) -[![Total Downloads](https://img.shields.io/packagist/dt/williamug/versioning.svg?style=flat-square)](https://packagist.org/packages/williamug/versioning/stats) -[![Made With](https://img.shields.io/badge/made_with-php-blue)](/docs/requirements/) -[![License](https://img.shields.io/packagist/l/williamug/versioning.svg)](https://github.com/williamug/versioning/blob/master/LICENSE.txt) +[![Latest Version on Packagist](https://img.shields.io/packagist/v/williamug/versioning.svg?style=flat-square)](https://packagist.org/packages/williamug/versioning) +[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/williamug/versioning/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/williamug/versioning/actions?query=workflow%3Arun-tests+branch%3Amain) +[![Total Downloads](https://img.shields.io/packagist/dt/williamug/versioning.svg?style=flat-square)](https://packagist.org/packages/williamug/versioning) +[![License](https://img.shields.io/packagist/l/williamug/versioning.svg?style=flat-square)](https://github.com/williamug/versioning/blob/master/LICENSE.md) +A robust PHP package that helps you display your application's version by leveraging Git tags. Features include caching, multiple format options, error handling, and comprehensive framework integration. +**Works with ANY PHP framework or vanilla PHP!** Laravel, Symfony, CodeIgniter, CakePHP, Slim, and more. -A PHP package to helps you to display the current version of your application by applying git version tags +> **Quick Links:** +> - **Which Class to Use?** [WHICH-CLASS.md](WHICH-CLASS.md) - Decision guide +> - **Vanilla PHP**: [VANILLA-PHP-USAGE.md](VANILLA-PHP-USAGE.md) - Standalone usage +> - **Framework Integration**: [FRAMEWORK-INTEGRATION.md](FRAMEWORK-INTEGRATION.md) - 8+ frameworks +> - **Supported**: Laravel, Symfony, CodeIgniter, CakePHP, Slim, Yii2, Laminas, Phalcon + +## Features + +- **Multiple Version Formats**: Tag, full, commit hash, or tag with commit +- **Performance**: Built-in caching support to minimize Git command executions +- **Secure**: Proper input sanitization and error handling +- **Universal Integration**: Works with Laravel, Symfony, CodeIgniter, CakePHP, Slim, and more +- **Configurable**: Extensive configuration options +- **Cache Support**: PSR-6, PSR-16, and framework-specific caches +- **Well-tested**: Comprehensive test coverage +- **Zero Dependencies**: Works standalone with vanilla PHP or any framework + +## Requirements + +- PHP 8.2 or higher +- Any PHP framework (Laravel, Symfony, CodeIgniter, etc.) or vanilla PHP +- Git installed on your system +- Optional: PSR-6 or PSR-16 compatible cache for caching ## Installation -You can install the package via composer: +Install the package via Composer: ```bash composer require williamug/versioning ``` +### Laravel Configuration (Optional) + +Publish the configuration file: + +```bash +php artisan vendor:publish --tag="versioning-config" +``` + +This creates `config/versioning.php` where you can customize: + +```php +return [ + 'repository_path' => base_path(), + 'cache' => [ + 'enabled' => true, + 'ttl' => 3600, // 1 hour + 'key' => 'app_version', + ], + 'fallback_version' => env('APP_VERSION', 'dev'), + 'format' => 'tag', + 'include_prefix' => true, +]; +``` ## Usage -#### For Vanilla PHP -If your project is written in vanilla PHP you can use the following code to display the version of your application: +### Vanilla PHP + +#### Option 1: Using the Helper Function (Simplest) + ```php +require __DIR__ . '/vendor/autoload.php'; -require __DIR__ . '/vendor/williamug/versioning/src/functions.php'; +// Simple usage +echo app_version(); // v1.0.0 -// after requiring the function file you can now use the app_versioning() function to display the version of your application -app_versioning(); -//v1.0.0 +// Different formats +echo app_version('tag'); // v1.0.0 +echo app_version('full'); // v1.0.0-5-g123abc +echo app_version('commit'); // 123abc +echo app_version('tag-commit'); // v1.0.0-123abc ``` -#### For Laravel -If you are using Laravel you can use the following code to display the version of your application: +#### Option 2: Using the Standalone Class (More Features) + +```php +require __DIR__ . '/vendor/autoload.php'; + +use Williamug\Versioning\StandaloneVersioning; + +// Configure (optional) +StandaloneVersioning::setRepositoryPath(__DIR__); +StandaloneVersioning::setFallbackVersion('1.0.0'); +StandaloneVersioning::setCaching(true, 3600); +StandaloneVersioning::setIncludePrefix(true); + +// Get version +echo StandaloneVersioning::tag(); // v1.0.0 +echo StandaloneVersioning::full(); // v1.0.0-5-g123abc +echo StandaloneVersioning::commit(); // 123abc +echo StandaloneVersioning::tagWithCommit(); // v1.0.0-123abc + +// Clear cache when needed +StandaloneVersioning::clearCache(); +``` + +#### Option 3: Universal Framework Class (Works with Any Framework) + +```php +use Williamug\Versioning\UniversalVersioning; + +// Configure with your framework's cache +UniversalVersioning::setRepositoryPath(__DIR__); +UniversalVersioning::setCacheAdapter($yourFrameworkCache); // PSR-6/PSR-16 compatible +UniversalVersioning::setFallbackVersion('1.0.0'); + +echo UniversalVersioning::tag(); // v1.0.0 +``` + +### Other PHP Frameworks + +The package works seamlessly with **any PHP framework**! See **[FRAMEWORK-INTEGRATION.md](FRAMEWORK-INTEGRATION.md)** for detailed examples: + +- **Symfony** - Full integration with Symfony Cache +- **CodeIgniter 4** - Library and helper examples +- **CakePHP 5** - Component and helper integration +- **Slim 4** - Middleware and DI container setup +- **Yii2** - Component configuration +- **Laminas** - Service manager integration +- **Phalcon** - DI service registration + +### Laravel + +#### Using the Facade ```php -Williamug\Versioning\Versioning::tag() -// v1.0.0 +use Williamug\Versioning\Versioning; + +// Get version tag +Versioning::tag(); // v1.0.0 + +// Get full version info +Versioning::full(); // v1.0.0-5-g123abc + +// Get commit hash +Versioning::commit(); // 123abc + +// Get tag with commit +Versioning::tagWithCommit(); // v1.0.0-123abc + +// Clear version cache +Versioning::clearCache(); ``` -#### For Laravel Blade -If you are using Laravel Blade you can use the following code to display the version of your application: +#### Using Blade Directives + ```blade +{{-- Simple tag version --}} + + +{{-- Full version info --}}
- @app_version + Build: @app_version_full
+ +{{-- Just the commit hash --}} +
+ Commit: @app_version_commit +
+ +{{-- Custom format --}} +
+ Version: @app_version('tag-commit') +
+``` + +#### Using the Helper Function + +```php +// In your controllers or views +$version = app_version(); +$commit = app_version('commit'); +``` + +## Configuration Options + +### Repository Path + +Specify where your `.git` directory is located: + +```php +'repository_path' => base_path(), // or any absolute path ``` +### Caching + +Enable caching to improve performance: + +```php +'cache' => [ + 'enabled' => true, + 'ttl' => 3600, // Cache for 1 hour + 'key' => 'app_version', +], +``` + +### Fallback Version + +Set a default version when Git is unavailable: + +```php +'fallback_version' => env('APP_VERSION', 'dev'), +``` + +You can set this in your `.env`: + +```env +APP_VERSION=v1.0.0 +``` + +### Version Format + +Choose default format: + +```php +'format' => 'tag', // Options: 'tag', 'full', 'commit', 'tag-commit' +``` + +### Version Prefix + +Control whether to include 'v' prefix: + +```php +'include_prefix' => false, // Displays: 1.0.0 instead of v1.0.0 +``` + +## Error Handling + +The package gracefully handles errors: + +- Returns fallback version if Git is not installed +- Returns fallback version if not in a Git repository +- Returns fallback version if no tags exist +- Catches and handles all exceptions + ## Testing ```bash +# Run tests composer test + +# Run tests with coverage +composer test-coverage + +# Run static analysis +composer analyse + +# Format code +composer format +``` + +## Development + +```bash +# Install dependencies +composer install + +# Run Pint (code formatting) +vendor/bin/pint + +# Run PHPStan (static analysis) +vendor/bin/phpstan analyse + +# Run Pest (tests) +vendor/bin/pest +``` + +## Common Use Cases + +### Display Version in Footer + +```blade + +``` + +### API Response + +```php +public function version() +{ + return response()->json([ + 'version' => Versioning::tag(), + 'commit' => Versioning::commit(), + 'build_date' => now(), + ]); +} +``` + +### Admin Dashboard + +```php +public function dashboard() +{ + return view('admin.dashboard', [ + 'app_version' => Versioning::full(), + 'git_commit' => Versioning::commit(), + ]); +} +``` + +### Clear Cache After Deployment + +```php +// In your deployment script +Artisan::call('cache:clear'); +Versioning::clearCache(); +``` + +## Troubleshooting + +### "dev" is always displayed + +- Ensure you're in a Git repository +- Ensure Git is installed: `git --version` +- Ensure you have tags: `git tag` +- Check your repository path in config + +### Create a tag if none exist + +```bash +git tag v1.0.0 +git push origin v1.0.0 ``` ## Changelog diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..e2a4aa9 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +## Supported Versions + +We release patches for security vulnerabilities. Currently supported versions: + +| Version | Supported | +| ------- | ------------------ | +| 3.x | :white_check_mark: | +| 2.x | :x: | +| 1.x | :x: | + +## Reporting a Vulnerability + +Please report (suspected) security vulnerabilities to **asabawilliamdk@yahoo.com**, create a GitHub issue or submit a pull request. + +Please do not report security vulnerabilities through public GitHub issues. + +## What to Include + +When reporting a vulnerability, please include: + +- Type of issue (e.g. command injection, XSS, etc.) +- Full paths of source file(s) related to the issue +- Location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- Step-by-step instructions to reproduce the issue +- Proof-of-concept or exploit code (if possible) +- Impact of the issue, including how an attacker might exploit it + diff --git a/VANILLA-PHP-USAGE.md b/VANILLA-PHP-USAGE.md new file mode 100644 index 0000000..182611c --- /dev/null +++ b/VANILLA-PHP-USAGE.md @@ -0,0 +1,242 @@ +# Vanilla PHP Quick Reference + +This guide shows you how to use the Versioning package in vanilla PHP projects without Laravel. + +## Installation + +```bash +composer require williamug/versioning +``` + +## Quick Start + +### Method 1: Simple Helper Function + +```php + +``` + +### Method 2: Full-Featured Class + +```php + +``` + +## Helper Function API + +```php +app_version() // Returns: v1.0.0 +app_version('tag') // Returns: v1.0.0 +app_version('full') // Returns: v1.0.0-5-g123abc +app_version('commit') // Returns: 123abc +app_version('tag-commit') // Returns: v1.0.0-123abc +``` + +## StandaloneVersioning API + +### Getting Version Info + +```php +use Williamug\Versioning\StandaloneVersioning; + +StandaloneVersioning::tag(); // v1.0.0 +StandaloneVersioning::full(); // v1.0.0-5-g123abc +StandaloneVersioning::commit(); // 123abc +StandaloneVersioning::tagWithCommit(); // v1.0.0-123abc +StandaloneVersioning::getVersion('tag'); // v1.0.0 +``` + +### Configuration + +```php +// Set repository path (default: current directory) +StandaloneVersioning::setRepositoryPath('/path/to/repo'); + +// Enable/disable caching and set TTL +StandaloneVersioning::setCaching(true, 3600); // Cache for 1 hour + +// Set fallback version when Git is unavailable +StandaloneVersioning::setFallbackVersion('1.0.0'); + +// Include or remove 'v' prefix +StandaloneVersioning::setIncludePrefix(true); // v1.0.0 +StandaloneVersioning::setIncludePrefix(false); // 1.0.0 + +// Clear cache +StandaloneVersioning::clearCache(); +``` + +## Common Use Cases + +### Display Version in HTML Footer + +```php + +``` + +### API Response + +```php +header('Content-Type: application/json'); +echo json_encode([ + 'version' => app_version('tag'), + 'commit' => app_version('commit'), + 'build_date' => date('Y-m-d H:i:s') +]); +``` + +### Configuration File + +```php +// config.php +return [ + 'app_name' => 'My Application', + 'version' => app_version(), + 'debug' => false, +]; +``` + +### About Page + +```php +use Williamug\Versioning\StandaloneVersioning; + +StandaloneVersioning::setCaching(true, 3600); + +$versionInfo = [ + 'tag' => StandaloneVersioning::tag(), + 'full' => StandaloneVersioning::full(), + 'commit' => StandaloneVersioning::commit(), +]; +?> +

About

+
+
Version:
+
+ +
Build:
+
+ +
Commit:
+
+
+``` + +### Environment-Based Fallback + +```php +// Set different fallback for development vs production +$fallback = getenv('APP_ENV') === 'production' ? '1.0.0' : 'dev'; +StandaloneVersioning::setFallbackVersion($fallback); + +echo StandaloneVersioning::tag(); +``` + +### Custom Repository Path + +```php +// If your .git folder is not in the current directory +StandaloneVersioning::setRepositoryPath('/var/www/my-app'); +echo StandaloneVersioning::tag(); +``` + +## Error Handling + +Both methods gracefully handle errors: + +- Returns fallback version if Git is not installed +- Returns fallback version if not in a Git repository +- Returns fallback version if no tags exist +- Catches all exceptions automatically + +```php +// Will return 'dev' if Git is unavailable +echo app_version(); // dev + +// With custom fallback +StandaloneVersioning::setFallbackVersion('unknown'); +echo StandaloneVersioning::tag(); // unknown +``` + +## Performance Tips + +1. **Enable Caching**: Reduces Git command executions + ```php + StandaloneVersioning::setCaching(true, 3600); + ``` + +2. **Set Repository Path**: Avoids repeated directory checks + ```php + StandaloneVersioning::setRepositoryPath(__DIR__); + ``` + +3. **Clear Cache After Deployment**: Ensure fresh version info + ```php + StandaloneVersioning::clearCache(); + ``` + +## Complete Example + +```php + + + + + My App v<?php echo $version; ?> + + +

Welcome to My App

+ + + +``` + +## Differences from Laravel Version + +| Feature | Vanilla PHP | Laravel | +|---------|-------------|---------| +| Caching | In-memory (per request) | Laravel Cache (persistent) | +| Configuration | Static methods | Config file | +| Blade Directives | ❌ | ✅ | +| Helper Function | ✅ | ✅ | +| Class Methods | ✅ | ✅ | + +## Need Laravel Features? + +If you need persistent caching, Blade directives, and configuration files, use the Laravel version: + +```php +use Williamug\Versioning\Versioning; + +echo Versioning::tag(); +``` + +See the main README.md for Laravel-specific documentation. diff --git a/WHICH-CLASS.md b/WHICH-CLASS.md new file mode 100644 index 0000000..c287937 --- /dev/null +++ b/WHICH-CLASS.md @@ -0,0 +1,219 @@ +# Which Class Should I Use? + +Choose the right versioning class for your project: + +## Quick Comparison + +| Class | Best For | Cache Support | Configuration | Laravel Features | +|-------|----------|---------------|---------------|-----------------| +| **UniversalVersioning** | Any framework with cache | PSR-6, PSR-16, framework-specific | Static methods | ❌ | +| **StandaloneVersioning** | Vanilla PHP, no cache | In-memory (per-request) | Static methods | ❌ | +| **Versioning** | Laravel projects | Laravel Cache (persistent) | Config file | ✅ Blade, Facades | + +## Decision Tree + +``` +Do you use Laravel? +├─ Yes → Use Versioning class (full Laravel integration) +│ +└─ No → Do you have a cache system? + ├─ Yes → Use UniversalVersioning (works with any cache) + └─ No → Use StandaloneVersioning (works without cache) +``` + +## Detailed Breakdown + +### UniversalVersioning + +**Use when:** +- Using Symfony, CodeIgniter, CakePHP, Slim, Yii2, Laminas, or Phalcon +- Have PSR-6 or PSR-16 compatible cache +- Want persistent caching across requests +- Working with any modern PHP framework + +**Features:** +- Auto-detects cache interface (PSR-6/PSR-16) +- Works with any framework's cache system +- Persistent caching across requests +- Full configuration via static methods + +**Example:** +```php +use Williamug\Versioning\UniversalVersioning; + +UniversalVersioning::setRepositoryPath(__DIR__); +UniversalVersioning::setCacheAdapter($yourFrameworkCache); +UniversalVersioning::setFallbackVersion('1.0.0'); + +echo UniversalVersioning::tag(); // v1.0.0 +``` + +--- + +### StandaloneVersioning + +**Use when:** +- Building vanilla PHP applications +- No cache system available +- Prototyping or simple projects +- Don't need persistent caching + +**Features:** +- Zero dependencies (no framework, no cache) +- In-memory caching (per PHP request) +- Simple static method configuration +- Works anywhere PHP runs + +**Example:** +```php +use Williamug\Versioning\StandaloneVersioning; + +StandaloneVersioning::setRepositoryPath(__DIR__); +StandaloneVersioning::setCaching(true, 3600); +StandaloneVersioning::setFallbackVersion('1.0.0'); + +echo StandaloneVersioning::tag(); // v1.0.0 +``` + +--- + +### Versioning (Laravel) + +**Use when:** +- Working with Laravel 10+ projects +- Want config file integration +- Need Blade directives +- Want facade support + +**Features:** +- Full Laravel integration +- Config file: `config/versioning.php` +- Blade directives: `@app_version_tag`, etc. +- Facade support: `Versioning::tag()` +- Laravel Cache integration (Redis, File, etc.) +- Service provider auto-registration + +**Example:** +```php +use Williamug\Versioning\Versioning; + +echo Versioning::tag(); // v1.0.0 +``` + +**Blade:** +```blade + +``` + +--- + +## Feature Comparison + +| Feature | Universal | Standalone | Laravel | +|---------|-----------|------------|---------| +| **Caching** | ||| +| Persistent cache | ✅ | ❌ | ✅ | +| In-memory cache | ❌ | ✅ | ❌ | +| PSR-6 support | ✅ | ❌ | ✅ | +| PSR-16 support | ✅ | ❌ | ✅ | +| **Configuration** | ||| +| Static methods | ✅ | ✅ | ❌ | +| Config file | ❌ | ❌ | ✅ | +| Environment vars | ✅ | ✅ | ✅ | +| **Framework Integration** | ||| +| Vanilla PHP | ✅ | ✅ | ❌ | +| Symfony | ✅ | ❌ | ❌ | +| CodeIgniter | ✅ | ❌ | ❌ | +| CakePHP | ✅ | ❌ | ❌ | +| Slim | ✅ | ❌ | ❌ | +| Laravel | ✅ | ❌ | ✅ | +| Yii2 | ✅ | ❌ | ❌ | +| Laminas | ✅ | ❌ | ❌ | +| Phalcon | ✅ | ❌ | ❌ | +| **Laravel Features** | ||| +| Blade directives | ❌ | ❌ | ✅ | +| Facades | ❌ | ❌ | ✅ | +| Service provider | ❌ | ❌ | ✅ | +| Config publishing | ❌ | ❌ | ✅ | +| **Version Formats** | ||| +| Tag | ✅ | ✅ | ✅ | +| Full | ✅ | ✅ | ✅ | +| Commit | ✅ | ✅ | ✅ | +| Tag + commit | ✅ | ✅ | ✅ | +| **Error Handling** | ||| +| Fallback version | ✅ | ✅ | ✅ | +| Exception handling | ✅ | ✅ | ✅ | +| Graceful degradation | ✅ | ✅ | ✅ | + +--- + +## Common Scenarios + +### Scenario 1: Symfony Project +**Use:** `UniversalVersioning` +```php +UniversalVersioning::setCacheAdapter($cache); // Symfony Cache +``` + +### Scenario 2: Simple PHP Website +**Use:** `StandaloneVersioning` +```php +echo StandaloneVersioning::tag(); +``` + +### Scenario 3: Laravel API +**Use:** `Versioning` (Laravel class) +```php +return ['version' => Versioning::tag()]; +``` + +### Scenario 4: CodeIgniter App +**Use:** `UniversalVersioning` +```php +UniversalVersioning::setCacheAdapter(\Config\Services::cache()); +``` + +### Scenario 5: Legacy PHP (No Framework) +**Use:** `StandaloneVersioning` or helper function +```php +echo app_version(); // Simple helper +``` + +--- + +## Migration Between Classes + +### From StandaloneVersioning → UniversalVersioning + +```php +// Before +StandaloneVersioning::tag(); + +// After (add cache support) +UniversalVersioning::setCacheAdapter($cache); +UniversalVersioning::tag(); +``` + +### From UniversalVersioning → Versioning (Laravel) + +```php +// Before +UniversalVersioning::tag(); + +// After (use Laravel class) +Versioning::tag(); + +// Or in Blade +@app_version_tag +``` + +--- + +## Still Not Sure? + +- **Just getting started?** Use `app_version()` helper function +- **Need simple solution?** Use `StandaloneVersioning` +- **Have a cache system?** Use `UniversalVersioning` +- **Using Laravel?** Use `Versioning` class with Blade directives + +All classes provide the same core functionality - just pick the one that fits your project best! 🚀 diff --git a/build/report.junit.xml b/build/report.junit.xml index 4b5d9ac..b333324 100644 --- a/build/report.junit.xml +++ b/build/report.junit.xml @@ -1,12 +1,72 @@ - - - - + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/composer.json b/composer.json index 63e499d..1ea7853 100644 --- a/composer.json +++ b/composer.json @@ -1,80 +1,87 @@ { - "name": "williamug/versioning", - "description": "A PHP package to helps you to display the version of your application by applying git version tags", - "keywords": [ - "Williamug", - "laravel", - "PHP", - "versioning" + "name": "williamug/versioning", + "description": "A PHP package to helps you to display the version of your application by applying git version tags", + "keywords": [ + "Williamug", + "laravel", + "PHP", + "versioning" + ], + "homepage": "https://github.com/williamug/versioning", + "license": "MIT", + "authors": [ + { + "name": "Asaba William", + "email": "asabawilliamdk@yahoo.com", + "role": "Developer" + } + ], + "require": { + "php": "^8.2", + "spatie/laravel-package-tools": "^1.16", + "illuminate/contracts": "^10.0||^11.0" + }, + "require-dev": { + "laravel/pint": "^1.18", + "nunomaduro/collision": "^8.1.1||^7.10.0", + "orchestra/testbench": "^9.0.0||^8.22.0", + "pestphp/pest": "^2.35||^3.0", + "pestphp/pest-plugin-arch": "^2.7||^3.0", + "pestphp/pest-plugin-laravel": "^2.4||^3.0", + "phpstan/phpstan": "^1.12", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan-phpunit": "^1.4", + "larastan/larastan": "^2.9" + }, + "autoload": { + "files": [ + "src/functions.php" ], - "homepage": "https://github.com/williamug/versioning", - "license": "MIT", - "authors": [ - { - "name": "Asaba William", - "email": "asabawilliamdk@yahoo.com", - "role": "Developer" - } + "psr-4": { + "Williamug\\Versioning\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Williamug\\Versioning\\Tests\\": "tests/", + "Workbench\\App\\": "workbench/app/" + } + }, + "scripts": { + "post-autoload-dump": "@composer run prepare", + "clear": "@php vendor/bin/testbench package:purge-versioning --ansi", + "prepare": "@php vendor/bin/testbench package:discover --ansi", + "build": [ + "@composer run prepare", + "@php vendor/bin/testbench workbench:build --ansi" ], - "require": { - "php": "^8.2", - "spatie/laravel-package-tools": "^1.16", - "illuminate/contracts": "^10.0||^11.0" - }, - "require-dev": { - "laravel/pint": "^1.14", - "nunomaduro/collision": "^8.1.1||^7.10.0", - "orchestra/testbench": "^9.0.0||^8.22.0", - "pestphp/pest": "^2.34", - "pestphp/pest-plugin-arch": "^2.7", - "pestphp/pest-plugin-laravel": "^2.3" - }, - "autoload": { - "psr-4": { - "Williamug\\Versioning\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Williamug\\Versioning\\Tests\\": "tests/", - "Workbench\\App\\": "workbench/app/" - } - }, - "scripts": { - "post-autoload-dump": "@composer run prepare", - "clear": "@php vendor/bin/testbench package:purge-versioning --ansi", - "prepare": "@php vendor/bin/testbench package:discover --ansi", - "build": [ - "@composer run prepare", - "@php vendor/bin/testbench workbench:build --ansi" - ], - "start": [ - "Composer\\Config::disableProcessTimeout", - "@composer run build", - "@php vendor/bin/testbench serve" - ], - "analyse": "vendor/bin/phpstan analyse", - "test": "vendor/bin/pest", - "test-coverage": "vendor/bin/pest --coverage", - "format": "vendor/bin/pint" - }, - "config": { - "sort-packages": true, - "allow-plugins": { - "pestphp/pest-plugin": true, - "phpstan/extension-installer": true - } - }, - "extra": { - "laravel": { - "providers": [ - "Williamug\\Versioning\\VersioningServiceProvider" - ], - "aliases": { - "Versioning": "Williamug\\Versioning\\Facades\\Versioning" - } - } - }, - "minimum-stability": "stable", - "prefer-stable": true -} \ No newline at end of file + "start": [ + "Composer\\Config::disableProcessTimeout", + "@composer run build", + "@php vendor/bin/testbench serve" + ], + "analyse": "vendor/bin/phpstan analyse", + "test": "vendor/bin/pest", + "test-coverage": "vendor/bin/pest --coverage", + "format": "vendor/bin/pint" + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true, + "phpstan/extension-installer": true + } + }, + "extra": { + "laravel": { + "providers": [ + "Williamug\\Versioning\\VersioningServiceProvider" + ], + "aliases": { + "Versioning": "Williamug\\Versioning\\Facades\\Versioning" + } + } + }, + "minimum-stability": "stable", + "prefer-stable": true +} diff --git a/config/skeleton.php b/config/skeleton.php deleted file mode 100644 index 7e74186..0000000 --- a/config/skeleton.php +++ /dev/null @@ -1,6 +0,0 @@ - base_path(), + + /* + |-------------------------------------------------------------------------- + | Cache Settings + |-------------------------------------------------------------------------- + | + | Enable caching to improve performance by reducing git command executions. + | Cache TTL is specified in seconds. + | + */ + 'cache' => [ + 'enabled' => env('VERSIONING_CACHE_ENABLED', true), + 'ttl' => env('VERSIONING_CACHE_TTL', 3600), // 1 hour + 'key' => 'app_version', + ], + + /* + |-------------------------------------------------------------------------- + | Fallback Version + |-------------------------------------------------------------------------- + | + | The version to display when git information is unavailable or + | when an error occurs during version retrieval. + | + */ + 'fallback_version' => env('APP_VERSION', 'dev'), + + /* + |-------------------------------------------------------------------------- + | Version Format + |-------------------------------------------------------------------------- + | + | Customize how the version is displayed: + | - 'tag' - Show only the tag (e.g., v1.0.0) + | - 'tag-commit' - Show tag with commit hash (e.g., v1.0.0-abc1234) + | - 'full' - Show full git describe output (e.g., v1.0.0-5-abc1234) + | + */ + 'format' => env('VERSIONING_FORMAT', 'tag'), + + /* + |-------------------------------------------------------------------------- + | Include Prefix + |-------------------------------------------------------------------------- + | + | Whether to include the 'v' prefix in version numbers. + | Set to false to display '1.0.0' instead of 'v1.0.0' + | + */ + 'include_prefix' => true, +]; diff --git a/examples/cakephp-integration.php b/examples/cakephp-integration.php new file mode 100644 index 0000000..6fe6f8c --- /dev/null +++ b/examples/cakephp-integration.php @@ -0,0 +1,141 @@ +loadComponent('Versioning'); + } + + public function beforeRender(\Cake\Event\EventInterface $event) + { + parent::beforeRender($event); + + // Make version available to all views + $this->set('appVersion', $this->Versioning->getVersion()); + $this->set('appCommit', $this->Versioning->getCommit()); + } +} +*/ + +// Create a Helper (src/View/Helper/VersionHelper.php): +/* + +

Version: Version->tag() ?>

+

Build: Version->commit() ?>

+ +*/ + +// API Endpoint (src/Controller/ApiController.php): +/* +viewBuilder()->setOption('serialize', ['version']); + + $version = [ + 'tag' => $this->Versioning->getVersion(), + 'full' => $this->Versioning->getFull(), + 'commit' => $this->Versioning->getCommit(), + ]; + + $this->set('version', $version); + } +} +*/ diff --git a/examples/codeigniter-integration.php b/examples/codeigniter-integration.php new file mode 100644 index 0000000..dee4039 --- /dev/null +++ b/examples/codeigniter-integration.php @@ -0,0 +1,118 @@ +cache = \Config\Services::cache(); + + // Configure UniversalVersioning + UniversalVersioning::setRepositoryPath(ROOTPATH); + UniversalVersioning::setCacheAdapter($this->cache); + UniversalVersioning::setFallbackVersion(env('app.version', 'dev')); + UniversalVersioning::setCacheTtl(3600); + } + + public function getVersion(): string + { + return UniversalVersioning::tag(); + } + + public function getFull(): string + { + return UniversalVersioning::full(); + } + + public function getCommit(): string + { + return UniversalVersioning::commit(); + } + + public function clearCache(): void + { + UniversalVersioning::clearCache(); + } +} + +// In your controller (app/Controllers/Home.php): +/* + $versioning->getVersion(), + 'commit' => $versioning->getCommit(), + ]; + + return view('welcome_message', $data); + } + + public function apiVersion() + { + $versioning = new Versioning(); + + return $this->response->setJSON([ + 'version' => $versioning->getVersion(), + 'full' => $versioning->getFull(), + 'commit' => $versioning->getCommit(), + ]); + } +} +*/ + +// In your views (app/Views/welcome_message.php): +/* +
+

Version:

+

Build:

+
+*/ + +// Create a helper (app/Helpers/version_helper.php): +/* + $versioning->getVersion(), + 'full' => $versioning->getFull(), + 'commit' => $versioning->getCommit(), + default => $versioning->getVersion(), + }; + } +} +*/ + +// Load the helper in BaseController or autoload: +/* +// In app/Config/Autoload.php +public $helpers = ['version']; + +// Then use anywhere: +echo get_app_version(); // v1.0.0 +*/ diff --git a/examples/index.php b/examples/index.php new file mode 100644 index 0000000..8fdc67c --- /dev/null +++ b/examples/index.php @@ -0,0 +1,97 @@ + + + + + + + My Application + + + + + + +
+

🚀 My Application

+ +
+

Version Information

+

Current Version:

+

Build Info:

+

Commit Hash:

+
+ +

About This Application

+

+ This is a demonstration of how to use the Versioning package in a vanilla PHP application. + The version information is automatically retrieved from your Git repository. +

+ +

Features

+
    +
  • Automatic version detection from Git tags
  • +
  • Multiple format options (tag, full, commit)
  • +
  • Built-in caching for better performance
  • +
  • Graceful fallback when Git is unavailable
  • +
+ +
+

+ Application Version: + | Build: +

+

+ © Your Company. All rights reserved. +

+
+
+ + + diff --git a/examples/slim-integration.php b/examples/slim-integration.php new file mode 100644 index 0000000..45adcd3 --- /dev/null +++ b/examples/slim-integration.php @@ -0,0 +1,96 @@ +get('cache')); + +// Add versioning to dependency container +$container = $app->getContainer(); +$container->set('version', function () { + return [ + 'tag' => UniversalVersioning::tag(), + 'full' => UniversalVersioning::full(), + 'commit' => UniversalVersioning::commit(), + ]; +}); + +// Routes +$app->get('/', function (Request $request, Response $response) { + $version = UniversalVersioning::tag(); + $commit = UniversalVersioning::commit(); + + $html = << + + My Slim App + +

Welcome

+
+

Version: {$version}

+

Build: {$commit}

+
+ + + HTML; + + $response->getBody()->write($html); + + return $response; +}); + +$app->get('/api/version', function (Request $request, Response $response) { + $data = [ + 'version' => UniversalVersioning::tag(), + 'full' => UniversalVersioning::full(), + 'commit' => UniversalVersioning::commit(), + ]; + + $response->getBody()->write(json_encode($data)); + + return $response->withHeader('Content-Type', 'application/json'); +}); + +// Middleware to add version to all responses +$app->add(function (Request $request, $handler) { + $response = $handler->handle($request); + + return $response->withHeader('X-App-Version', UniversalVersioning::tag()); +}); + +// Using Twig template engine +/* +use Slim\Views\Twig; +use Slim\Views\TwigMiddleware; + +$twig = Twig::create(__DIR__ . '/../templates', ['cache' => false]); + +// Add version to all templates +$twig->getEnvironment()->addGlobal('app_version', UniversalVersioning::tag()); +$twig->getEnvironment()->addGlobal('app_commit', UniversalVersioning::commit()); + +$app->add(TwigMiddleware::create($app, $twig)); + +$app->get('/', function (Request $request, Response $response) use ($twig) { + return $twig->render($response, 'home.html.twig', [ + 'title' => 'Home' + ]); +}); +*/ + +$app->run(); diff --git a/examples/symfony-integration.php b/examples/symfony-integration.php new file mode 100644 index 0000000..ea7433d --- /dev/null +++ b/examples/symfony-integration.php @@ -0,0 +1,88 @@ +projectDir); + UniversalVersioning::setCacheAdapter($this->cache); + UniversalVersioning::setFallbackVersion($_ENV['APP_VERSION'] ?? 'dev'); + UniversalVersioning::setCacheTtl(3600); + } + + public function getVersion(): string + { + return UniversalVersioning::tag(); + } + + public function getFullVersion(): string + { + return UniversalVersioning::full(); + } + + public function getCommit(): string + { + return UniversalVersioning::commit(); + } +} + +// In your controller: +/* +namespace App\Controller; + +use App\Service\VersioningService; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; + +class DefaultController extends AbstractController +{ + #[Route('/')] + public function index(VersioningService $versioning): Response + { + return $this->render('index.html.twig', [ + 'version' => $versioning->getVersion(), + 'commit' => $versioning->getCommit(), + ]); + } + + #[Route('/api/version')] + public function version(VersioningService $versioning): Response + { + return $this->json([ + 'version' => $versioning->getVersion(), + 'full' => $versioning->getFullVersion(), + 'commit' => $versioning->getCommit(), + ]); + } +} +*/ + +// In your Twig templates: +/* +{# templates/base.html.twig #} +
+

Version: {{ versioning.version }}

+
+*/ diff --git a/examples/vanilla-php.php b/examples/vanilla-php.php new file mode 100644 index 0000000..93c3140 --- /dev/null +++ b/examples/vanilla-php.php @@ -0,0 +1,79 @@ + + + + + + My App + + + +
+

Version:

+

Build:

+
+ + + + $version, + 'time' => time(), + ]; + } + + return $version; + } + + /** + * Fetch version from git + */ + protected static function fetchVersion(string $format): string + { + try { + $repositoryPath = self::$repositoryPath ?? getcwd(); + + // Validate repository path exists and is accessible + if (!is_dir($repositoryPath) || !is_dir($repositoryPath.'/.git')) { + return self::$fallbackVersion; + } + + $command = self::buildGitCommand($format, $repositoryPath); + $output = []; + $returnCode = 0; + + // Execute command safely + exec($command.' 2>&1', $output, $returnCode); + + if ($returnCode !== 0 || empty($output[0])) { + return self::$fallbackVersion; + } + + $version = trim($output[0]); + + // Remove 'v' prefix if configured + if (!self::$includePrefix) { + $version = ltrim($version, 'v'); + } + + return $version; + } catch (\Throwable $e) { + return self::$fallbackVersion; + } + } + + /** + * Build git command based on format + */ + protected static function buildGitCommand(string $format, string $repositoryPath): string + { + $escapedPath = escapeshellarg($repositoryPath); + + return match ($format) { + 'tag' => "git -C {$escapedPath} describe --tags --abbrev=0", + 'full' => "git -C {$escapedPath} describe --tags", + 'commit' => "git -C {$escapedPath} rev-parse --short HEAD", + 'tag-commit' => "git -C {$escapedPath} describe --tags --always", + default => "git -C {$escapedPath} describe --tags --abbrev=0", + }; + } + + /** + * Clear version cache + */ + public static function clearCache(): void + { + self::$cache = []; + } +} diff --git a/src/UniversalVersioning.php b/src/UniversalVersioning.php new file mode 100644 index 0000000..2b39a22 --- /dev/null +++ b/src/UniversalVersioning.php @@ -0,0 +1,294 @@ +&1', $output, $returnCode); + + if ($returnCode !== 0 || empty($output[0])) { + return self::$fallbackVersion; + } + + $version = trim($output[0]); + + if (!self::$includePrefix) { + $version = ltrim($version, 'v'); + } + + return $version; + } catch (\Throwable $e) { + return self::$fallbackVersion; + } + } + + /** + * Build git command based on format + */ + protected static function buildGitCommand(string $format, string $repositoryPath): string + { + $escapedPath = escapeshellarg($repositoryPath); + + return match ($format) { + 'tag' => "git -C {$escapedPath} describe --tags --abbrev=0", + 'full' => "git -C {$escapedPath} describe --tags", + 'commit' => "git -C {$escapedPath} rev-parse --short HEAD", + 'tag-commit' => "git -C {$escapedPath} describe --tags --always", + default => "git -C {$escapedPath} describe --tags --abbrev=0", + }; + } + + /** + * Get value from cache (supports multiple cache systems) + */ + protected static function getFromCache(string $key): ?string + { + if (self::$cacheAdapter === null) { + return null; + } + + try { + // PSR-16 SimpleCache (Symfony, Laravel 5.8+) + if (method_exists(self::$cacheAdapter, 'get')) { + $value = self::$cacheAdapter->get($key); + + return $value !== null ? (string) $value : null; + } + + // PSR-6 Cache (Symfony) + if (method_exists(self::$cacheAdapter, 'getItem')) { + $item = self::$cacheAdapter->getItem($key); + + return $item->isHit() ? (string) $item->get() : null; + } + + // Laravel Cache + if (method_exists(self::$cacheAdapter, 'has')) { + return self::$cacheAdapter->has($key) ? self::$cacheAdapter->get($key) : null; + } + + // CodeIgniter Cache + if (method_exists(self::$cacheAdapter, 'getMetadata')) { + return self::$cacheAdapter->get($key) ?: null; + } + } catch (\Throwable $e) { + // Cache failure shouldn't break the application + return null; + } + + return null; + } + + /** + * Store value in cache (supports multiple cache systems) + */ + protected static function storeInCache(string $key, string $value): void + { + if (self::$cacheAdapter === null) { + return; + } + + try { + // PSR-16 SimpleCache (Symfony, Laravel 5.8+) + if (method_exists(self::$cacheAdapter, 'set')) { + self::$cacheAdapter->set($key, $value, self::$cacheTtl); + + return; + } + + // PSR-6 Cache (Symfony) + if (method_exists(self::$cacheAdapter, 'getItem')) { + $item = self::$cacheAdapter->getItem($key); + $item->set($value); + $item->expiresAfter(self::$cacheTtl); + self::$cacheAdapter->save($item); + + return; + } + + // Laravel Cache + if (method_exists(self::$cacheAdapter, 'put')) { + self::$cacheAdapter->put($key, $value, self::$cacheTtl); + + return; + } + + // CodeIgniter Cache + if (method_exists(self::$cacheAdapter, 'save')) { + self::$cacheAdapter->save($key, $value, self::$cacheTtl); + + return; + } + } catch (\Throwable $e) { + // Cache failure shouldn't break the application + } + } + + /** + * Clear version cache + */ + public static function clearCache(): void + { + if (self::$cacheAdapter === null) { + return; + } + + $formats = ['tag', 'full', 'commit', 'tag-commit']; + + foreach ($formats as $format) { + $key = "app_version_{$format}"; + + try { + // PSR-16 SimpleCache + if (method_exists(self::$cacheAdapter, 'delete')) { + self::$cacheAdapter->delete($key); + continue; + } + + // PSR-6 Cache + if (method_exists(self::$cacheAdapter, 'deleteItem')) { + self::$cacheAdapter->deleteItem($key); + continue; + } + + // Laravel Cache + if (method_exists(self::$cacheAdapter, 'forget')) { + self::$cacheAdapter->forget($key); + continue; + } + + // CodeIgniter Cache + if (method_exists(self::$cacheAdapter, 'delete')) { + self::$cacheAdapter->delete($key); + continue; + } + } catch (\Throwable $e) { + // Continue clearing other keys + } + } + } +} diff --git a/src/Versioning.php b/src/Versioning.php index 41f34f2..a637252 100755 --- a/src/Versioning.php +++ b/src/Versioning.php @@ -2,10 +2,136 @@ namespace Williamug\Versioning; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Config; + class Versioning { + /** + * Get the current version tag + */ public static function tag(): string { - return exec('git describe --tags --abbrev=0'); + return self::getVersion('tag'); + } + + /** + * Get the full version information + */ + public static function full(): string + { + return self::getVersion('full'); + } + + /** + * Get the commit hash + */ + public static function commit(): string + { + return self::getVersion('commit'); + } + + /** + * Get version with commit hash + */ + public static function tagWithCommit(): string + { + return self::getVersion('tag-commit'); + } + + /** + * Get version based on format + */ + public static function getVersion(string $format = 'tag'): string + { + $cacheEnabled = Config::get('versioning.cache.enabled', true); + $cacheKey = Config::get('versioning.cache.key', 'app_version')."_{$format}"; + $cacheTtl = Config::get('versioning.cache.ttl', 3600); + + if ($cacheEnabled && Cache::has($cacheKey)) { + return Cache::get($cacheKey); + } + + $version = self::fetchVersion($format); + + if ($cacheEnabled) { + Cache::put($cacheKey, $version, $cacheTtl); + } + + return $version; + } + + /** + * Fetch version from git + */ + protected static function fetchVersion(string $format): string + { + try { + $repositoryPath = Config::get('versioning.repository_path', base_path()); + + // Validate repository path exists and is accessible + if (!is_dir($repositoryPath) || !is_dir($repositoryPath.'/.git')) { + return self::getFallbackVersion(); + } + + $command = self::buildGitCommand($format, $repositoryPath); + $output = []; + $returnCode = 0; + + // Execute command safely + exec($command.' 2>&1', $output, $returnCode); + + if ($returnCode !== 0 || empty($output[0])) { + return self::getFallbackVersion(); + } + + $version = trim($output[0]); + + // Remove 'v' prefix if configured + if (!Config::get('versioning.include_prefix', true)) { + $version = ltrim($version, 'v'); + } + + return $version; + } catch (\Throwable $e) { + return self::getFallbackVersion(); + } + } + + /** + * Build git command based on format + */ + protected static function buildGitCommand(string $format, string $repositoryPath): string + { + $escapedPath = escapeshellarg($repositoryPath); + + return match ($format) { + 'tag' => "git -C {$escapedPath} describe --tags --abbrev=0", + 'full' => "git -C {$escapedPath} describe --tags", + 'commit' => "git -C {$escapedPath} rev-parse --short HEAD", + 'tag-commit' => "git -C {$escapedPath} describe --tags --always", + default => "git -C {$escapedPath} describe --tags --abbrev=0", + }; + } + + /** + * Get fallback version from config + */ + protected static function getFallbackVersion(): string + { + return Config::get('versioning.fallback_version', 'dev'); + } + + /** + * Clear version cache + */ + public static function clearCache(): void + { + $cacheKey = Config::get('versioning.cache.key', 'app_version'); + $formats = ['tag', 'full', 'commit', 'tag-commit']; + + foreach ($formats as $format) { + Cache::forget("{$cacheKey}_{$format}"); + } } } diff --git a/src/VersioningServiceProvider.php b/src/VersioningServiceProvider.php index 7bd5a1a..db24adb 100644 --- a/src/VersioningServiceProvider.php +++ b/src/VersioningServiceProvider.php @@ -10,16 +10,44 @@ class VersioningServiceProvider extends PackageServiceProvider { public function configurePackage(Package $package): void { - $package->name('versioning'); + $package + ->name('versioning') + ->hasConfigFile(); } /** * Bootstrap services. - * - * @return void */ - public function boot() + public function boot(): void { - Blade::directive('app_version', fn ($money) => ""); + parent::boot(); + + $this->registerBladeDirectives(); + } + + /** + * Register Blade directives + */ + protected function registerBladeDirectives(): void + { + // Main directive for tag version + Blade::directive('app_version', function ($format = null) { + $format = $format ?: "'tag'"; + + return ""; + }); + + // Additional helper directives + Blade::directive('app_version_tag', function () { + return ""; + }); + + Blade::directive('app_version_full', function () { + return ""; + }); + + Blade::directive('app_version_commit', function () { + return ""; + }); } } diff --git a/src/functions.php b/src/functions.php index be3525b..0ec6fd9 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,8 +1,45 @@ "git -C {$escapedPath} describe --tags --abbrev=0", + 'full' => "git -C {$escapedPath} describe --tags", + 'commit' => "git -C {$escapedPath} rev-parse --short HEAD", + 'tag-commit' => "git -C {$escapedPath} describe --tags --always", + default => "git -C {$escapedPath} describe --tags --abbrev=0", + }; + + $output = []; + $returnCode = 0; + + // Execute command safely + exec($command.' 2>&1', $output, $returnCode); + + if ($returnCode !== 0 || empty($output[0])) { + return 'dev'; + } + + return trim($output[0]); + } catch (\Throwable $e) { + return 'dev'; + } } } diff --git a/tests/BladeDirectivesTest.php b/tests/BladeDirectivesTest.php new file mode 100644 index 0000000..7c0c99e --- /dev/null +++ b/tests/BladeDirectivesTest.php @@ -0,0 +1,45 @@ +toHaveKey('app_version'); +}); + +it('registers app_version_tag blade directive', function () { + $directives = Blade::getCustomDirectives(); + + expect($directives)->toHaveKey('app_version_tag'); +}); + +it('registers app_version_full blade directive', function () { + $directives = Blade::getCustomDirectives(); + + expect($directives)->toHaveKey('app_version_full'); +}); + +it('registers app_version_commit blade directive', function () { + $directives = Blade::getCustomDirectives(); + + expect($directives)->toHaveKey('app_version_commit'); +}); + +it('app_version_tag directive outputs correct php code', function () { + $directive = Blade::compileString('@app_version_tag'); + + expect($directive)->toContain('Williamug\Versioning\Versioning::tag()'); +}); + +it('app_version_full directive outputs correct php code', function () { + $directive = Blade::compileString('@app_version_full'); + + expect($directive)->toContain('Williamug\Versioning\Versioning::full()'); +}); + +it('app_version_commit directive outputs correct php code', function () { + $directive = Blade::compileString('@app_version_commit'); + + expect($directive)->toContain('Williamug\Versioning\Versioning::commit()'); +}); diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php index 5d36321..e10b844 100644 --- a/tests/ExampleTest.php +++ b/tests/ExampleTest.php @@ -1,5 +1,130 @@ toBeTrue(); +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Config; +use Williamug\Versioning\Versioning; + +beforeEach(function () { + Config::set('versioning.repository_path', base_path()); + Config::set('versioning.cache.enabled', false); + Config::set('versioning.fallback_version', 'dev'); + Config::set('versioning.include_prefix', true); +}); + +it('returns fallback version when git is not available', function () { + Config::set('versioning.repository_path', '/nonexistent/path'); + + $version = Versioning::tag(); + + expect($version)->toBe('dev'); +}); + +it('returns fallback version when not in a git repository', function () { + Config::set('versioning.repository_path', sys_get_temp_dir()); + + $version = Versioning::tag(); + + expect($version)->toBe('dev'); +}); + +it('uses custom fallback version from config', function () { + Config::set('versioning.repository_path', '/nonexistent/path'); + Config::set('versioning.fallback_version', 'v0.0.0'); + + $version = Versioning::tag(); + + expect($version)->toBe('v0.0.0'); +}); + +it('can get version tag', function () { + $version = Versioning::tag(); + + expect($version)->toBeString(); +}); + +it('can get full version', function () { + $version = Versioning::full(); + + expect($version)->toBeString(); +}); + +it('can get commit hash', function () { + $version = Versioning::commit(); + + expect($version)->toBeString(); +}); + +it('can get tag with commit', function () { + $version = Versioning::tagWithCommit(); + + expect($version)->toBeString(); +}); + +it('caches version when cache is enabled', function () { + Config::set('versioning.cache.enabled', true); + Config::set('versioning.cache.ttl', 3600); + + Cache::shouldReceive('has') + ->with('app_version_tag') + ->once() + ->andReturn(false); + + Cache::shouldReceive('put') + ->with('app_version_tag', \Mockery::type('string'), 3600) + ->once(); + + Cache::shouldReceive('get') + ->never(); + + Versioning::tag(); +}); + +it('returns cached version when available', function () { + Config::set('versioning.cache.enabled', true); + + Cache::shouldReceive('has') + ->with('app_version_tag') + ->once() + ->andReturn(true); + + Cache::shouldReceive('get') + ->with('app_version_tag') + ->once() + ->andReturn('v1.0.0'); + + $version = Versioning::tag(); + + expect($version)->toBe('v1.0.0'); +}); + +it('can clear version cache', function () { + Config::set('versioning.cache.key', 'app_version'); + + Cache::shouldReceive('forget') + ->with('app_version_tag') + ->once(); + + Cache::shouldReceive('forget') + ->with('app_version_full') + ->once(); + + Cache::shouldReceive('forget') + ->with('app_version_commit') + ->once(); + + Cache::shouldReceive('forget') + ->with('app_version_tag-commit') + ->once(); + + Versioning::clearCache(); +}); + +it('removes version prefix when configured', function () { + Config::set('versioning.include_prefix', false); + Config::set('versioning.repository_path', base_path()); + + $version = Versioning::tag(); + + // If version starts with a number, prefix was removed + expect($version)->toMatch('/^(\d|dev)/'); }); diff --git a/tests/FunctionTest.php b/tests/FunctionTest.php new file mode 100644 index 0000000..1eb0e6b --- /dev/null +++ b/tests/FunctionTest.php @@ -0,0 +1,43 @@ +toBeTrue(); +}); + +it('app_version returns string', function () { + $version = app_version(); + + expect($version)->toBeString(); +}); + +it('app_version accepts format parameter', function () { + $tag = app_version('tag'); + $full = app_version('full'); + $commit = app_version('commit'); + + expect($tag)->toBeString(); + expect($full)->toBeString(); + expect($commit)->toBeString(); +}); + +it('app_version returns dev as fallback', function () { + // Create a temporary directory without git + $tempDir = sys_get_temp_dir().'/test_no_git_'.uniqid(); + mkdir($tempDir); + + $originalDir = getcwd(); + chdir($tempDir); + + $version = app_version(); + + chdir($originalDir); + rmdir($tempDir); + + expect($version)->toBe('dev'); +}); + +it('app_version handles invalid format gracefully', function () { + $version = app_version('invalid_format'); + + expect($version)->toBeString(); +}); diff --git a/tests/Pest.php b/tests/Pest.php index 7755db7..34b49bc 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -2,4 +2,7 @@ use Williamug\Versioning\Tests\TestCase; +// Load helper functions for tests +require_once __DIR__.'/../src/functions.php'; + uses(TestCase::class)->in(__DIR__); diff --git a/tests/StandaloneVersioningTest.php b/tests/StandaloneVersioningTest.php new file mode 100644 index 0000000..cfc6430 --- /dev/null +++ b/tests/StandaloneVersioningTest.php @@ -0,0 +1,123 @@ +toBeString(); +}); + +it('can get full version', function () { + $version = StandaloneVersioning::full(); + + expect($version)->toBeString(); +}); + +it('can get commit hash', function () { + $version = StandaloneVersioning::commit(); + + expect($version)->toBeString(); +}); + +it('can get tag with commit', function () { + $version = StandaloneVersioning::tagWithCommit(); + + expect($version)->toBeString(); +}); + +it('returns fallback version when git is not available', function () { + StandaloneVersioning::setRepositoryPath('/nonexistent/path'); + + $version = StandaloneVersioning::tag(); + + expect($version)->toBe('dev'); +}); + +it('uses custom fallback version', function () { + StandaloneVersioning::setRepositoryPath('/nonexistent/path'); + StandaloneVersioning::setFallbackVersion('v0.0.0'); + + $version = StandaloneVersioning::tag(); + + expect($version)->toBe('v0.0.0'); +}); + +it('can configure repository path', function () { + StandaloneVersioning::setRepositoryPath(base_path()); + + $version = StandaloneVersioning::tag(); + + expect($version)->toBeString(); +}); + +it('removes prefix when configured', function () { + StandaloneVersioning::setIncludePrefix(false); + StandaloneVersioning::setRepositoryPath(base_path()); + + $version = StandaloneVersioning::tag(); + + // If version starts with a number, prefix was removed + expect($version)->toMatch('/^(\d|dev)/'); +}); + +it('caches version when caching is enabled', function () { + StandaloneVersioning::setCaching(true, 3600); + StandaloneVersioning::setRepositoryPath(base_path()); + + $version1 = StandaloneVersioning::tag(); + $version2 = StandaloneVersioning::tag(); + + expect($version1)->toBe($version2); +}); + +it('can clear cache', function () { + StandaloneVersioning::setCaching(true, 3600); + StandaloneVersioning::setRepositoryPath(base_path()); + + $version1 = StandaloneVersioning::tag(); + + StandaloneVersioning::clearCache(); + + $version2 = StandaloneVersioning::tag(); + + expect($version1)->toBe($version2); +}); + +it('respects cache ttl', function () { + StandaloneVersioning::setCaching(true, 0); // Expire immediately + StandaloneVersioning::setRepositoryPath(base_path()); + + $version1 = StandaloneVersioning::tag(); + + sleep(1); // Wait for cache to expire + + $version2 = StandaloneVersioning::tag(); + + expect($version1)->toBe($version2); +}); + +it('handles invalid repository path gracefully', function () { + StandaloneVersioning::setRepositoryPath(''); + + $version = StandaloneVersioning::tag(); + + expect($version)->toBe('dev'); +}); + +it('handles all version formats', function () { + $formats = ['tag', 'full', 'commit', 'tag-commit']; + + foreach ($formats as $format) { + $version = StandaloneVersioning::getVersion($format); + expect($version)->toBeString(); + } +}); diff --git a/tests/TestCase.php b/tests/TestCase.php index fc38bf4..47be151 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -8,6 +8,8 @@ class TestCase extends Orchestra { + public static $latestResponse; + protected function setUp(): void { parent::setUp(); @@ -29,8 +31,8 @@ public function getEnvironmentSetUp($app) config()->set('database.default', 'testing'); /* - $migration = include __DIR__.'/../database/migrations/create_versioning_table.php.stub'; - $migration->up(); - */ + $migration = include __DIR__.'/../database/migrations/create_versioning_table.php.stub'; + $migration->up(); + */ } } diff --git a/tests/UniversalVersioningTest.php b/tests/UniversalVersioningTest.php new file mode 100644 index 0000000..e8d0229 --- /dev/null +++ b/tests/UniversalVersioningTest.php @@ -0,0 +1,292 @@ +toBeString(); +}); + +it('can get full version', function () { + $version = UniversalVersioning::full(); + + expect($version)->toBeString(); +}); + +it('can get commit hash', function () { + $version = UniversalVersioning::commit(); + + expect($version)->toBeString(); +}); + +it('can get tag with commit', function () { + $version = UniversalVersioning::tagWithCommit(); + + expect($version)->toBeString(); +}); + +it('returns fallback version when git is not available', function () { + UniversalVersioning::setRepositoryPath('/nonexistent/path'); + + $version = UniversalVersioning::tag(); + + expect($version)->toBe('dev'); +}); + +it('uses custom fallback version', function () { + UniversalVersioning::setRepositoryPath('/nonexistent/path'); + UniversalVersioning::setFallbackVersion('v0.0.0'); + + $version = UniversalVersioning::tag(); + + expect($version)->toBe('v0.0.0'); +}); + +it('can configure repository path', function () { + UniversalVersioning::setRepositoryPath(base_path()); + + $version = UniversalVersioning::tag(); + + expect($version)->toBeString(); +}); + +it('removes prefix when configured', function () { + UniversalVersioning::setIncludePrefix(false); + UniversalVersioning::setRepositoryPath(base_path()); + + $version = UniversalVersioning::tag(); + + expect($version)->toMatch('/^(\d|dev)/'); +}); + +it('works with psr-16 cache adapter', function () { + $cache = new class { + private $data = []; + + public function get($key, $default = null) + { + return $this->data[$key] ?? $default; + } + + public function set($key, $value, $ttl = null) + { + $this->data[$key] = $value; + + return true; + } + + public function delete($key) + { + unset($this->data[$key]); + + return true; + } + }; + + UniversalVersioning::setCacheAdapter($cache); + UniversalVersioning::setRepositoryPath(base_path()); + + $version1 = UniversalVersioning::tag(); + $version2 = UniversalVersioning::tag(); + + expect($version1)->toBe($version2); +}); + +it('works with psr-6 cache adapter', function () { + $cache = new class { + private $items = []; + + public function getItem($key) + { + return $this->items[$key] ?? new class($key) { + private $key; + + private $value = null; + + private $hit = false; + + public function __construct($key) + { + $this->key = $key; + } + + public function get() + { + return $this->value; + } + + public function set($value) + { + $this->value = $value; + $this->hit = true; + + return $this; + } + + public function isHit() + { + return $this->hit; + } + + public function expiresAfter($time) + { + return $this; + } + + public function getKey() + { + return $this->key; + } + }; + } + + public function save($item) + { + $this->items[$item->getKey()] = $item; + + return true; + } + + public function deleteItem($key) + { + unset($this->items[$key]); + + return true; + } + }; + + UniversalVersioning::setCacheAdapter($cache); + UniversalVersioning::setRepositoryPath(base_path()); + + $version = UniversalVersioning::tag(); + + expect($version)->toBeString(); +}); + +it('works with laravel-style cache', function () { + $cache = new class { + private $data = []; + + public function has($key) + { + return isset($this->data[$key]); + } + + public function get($key) + { + return $this->data[$key] ?? null; + } + + public function put($key, $value, $ttl) + { + $this->data[$key] = $value; + } + + public function forget($key) + { + unset($this->data[$key]); + } + }; + + UniversalVersioning::setCacheAdapter($cache); + UniversalVersioning::setRepositoryPath(base_path()); + + $version1 = UniversalVersioning::tag(); + $version2 = UniversalVersioning::tag(); + + expect($version1)->toBe($version2); +}); + +it('handles cache failures gracefully', function () { + $cache = new class { + public function get($key) + { + throw new \Exception('Cache failure'); + } + + public function set($key, $value, $ttl) + { + throw new \Exception('Cache failure'); + } + }; + + UniversalVersioning::setCacheAdapter($cache); + UniversalVersioning::setRepositoryPath(base_path()); + + $version = UniversalVersioning::tag(); + + expect($version)->toBeString(); +}); + +it('can clear cache with psr-16 adapter', function () { + $cache = new class { + public $deleted = []; + + public function get($key, $default = null) + { + return null; + } + + public function set($key, $value, $ttl = null) + { + return true; + } + + public function delete($key) + { + $this->deleted[] = $key; + + return true; + } + }; + + UniversalVersioning::setCacheAdapter($cache); + UniversalVersioning::clearCache(); + + expect($cache->deleted)->toContain('app_version_tag'); + expect($cache->deleted)->toContain('app_version_full'); + expect($cache->deleted)->toContain('app_version_commit'); + expect($cache->deleted)->toContain('app_version_tag-commit'); +}); + +it('can set custom cache ttl', function () { + UniversalVersioning::setCacheTtl(7200); + + // TTL is used internally, just verify it doesn't error + expect(true)->toBeTrue(); +}); + +it('handles all version formats', function () { + $formats = ['tag', 'full', 'commit', 'tag-commit']; + + foreach ($formats as $format) { + $version = UniversalVersioning::getVersion($format); + expect($version)->toBeString(); + } +}); + +it('handles invalid repository path gracefully', function () { + UniversalVersioning::setRepositoryPath(''); + + $version = UniversalVersioning::tag(); + + expect($version)->toBe('dev'); +}); + +it('works without cache adapter', function () { + UniversalVersioning::setCacheAdapter(null); + UniversalVersioning::setRepositoryPath(base_path()); + + $version = UniversalVersioning::tag(); + + expect($version)->toBeString(); +});