diff --git a/README.md b/README.md index c3af8f0..6ed9bac 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ This library allows to easily detect the PHP stack (Wordpress, Laravel, Symfony…) and the version used, when parsing a directory or ar Github remote repository. +The library is still in an early stage and subject to changes, but it follows SemVer. As long as you stick to a major version, you should be safe. + Supported Stacks for now: - Bolt CMS diff --git a/lib/DTO/NodeConfiguration.php b/lib/DTO/NodeConfiguration.php index d8a43f0..64de225 100644 --- a/lib/DTO/NodeConfiguration.php +++ b/lib/DTO/NodeConfiguration.php @@ -12,6 +12,8 @@ public function __construct( public ?string $version, public ?string $requirements, public NodePackageManagerType $packageManager, + /** @var array */ + public array $packageJsonContent, ) { } } diff --git a/lib/DTO/PhpConfiguration.php b/lib/DTO/PhpConfiguration.php index 3ac46f8..3ebe9ee 100644 --- a/lib/DTO/PhpConfiguration.php +++ b/lib/DTO/PhpConfiguration.php @@ -8,8 +8,12 @@ { public function __construct( public ?PhpVersion $phpVersion, - /** @var string[] $requiredExtensions */ + /** @var string[] */ public array $requiredExtensions = [], + /** @var array|null */ + public ?array $composerJsonContent = null, + /** @var array|null */ + public ?array $composerLockContent = null, ) { } } diff --git a/lib/NodeConfigurationDetector.php b/lib/NodeConfigurationDetector.php index 95d9070..a2ab87b 100644 --- a/lib/NodeConfigurationDetector.php +++ b/lib/NodeConfigurationDetector.php @@ -34,6 +34,7 @@ public function getNodeConfiguration( $this->getNodeVersion($baseUri, $subFolder), $this->getVersionRequirements($baseUri, $subFolder), $this->getPackageManagerType($baseUri, $subFolder), + $packageJsonConfig, ); } diff --git a/lib/PhpConfigurationDetector.php b/lib/PhpConfigurationDetector.php index d4109f6..a98ce5d 100644 --- a/lib/PhpConfigurationDetector.php +++ b/lib/PhpConfigurationDetector.php @@ -18,20 +18,31 @@ public function __construct(private ComposerConfigProvider $composerConfigProvid public function getPhpConfiguration(string $baseUri, ?string $subFolder = null): PhpConfiguration { - $composerConfig = $this->composerConfigProvider->getComposerConfig( + $composerJsonConfig = $this->composerConfigProvider->getComposerConfig( ComposerConfigType::JSON, $baseUri, $subFolder ); - if (null === $composerConfig) { - return new PhpConfiguration(null, []); + if (null === $composerJsonConfig) { + return new PhpConfiguration(null, [], null, null); } - $phpVersion = $this->getPhpVersion($composerConfig); - $requiredExtensions = $this->getRequiredExtensions($composerConfig); + $phpVersion = $this->getPhpVersion($composerJsonConfig); + $requiredExtensions = $this->getRequiredExtensions($composerJsonConfig); - return new PhpConfiguration($phpVersion, $requiredExtensions); + $composerLockConfig = $this->composerConfigProvider->getComposerConfig( + ComposerConfigType::LOCK, + $baseUri, + $subFolder + ); + + return new PhpConfiguration( + $phpVersion, + $requiredExtensions, + $composerJsonConfig->content, + $composerLockConfig?->content + ); } private function getPhpVersion(ComposerConfig $config): ?PhpVersion diff --git a/tests/Unit/DetectorTest.php b/tests/Unit/DetectorTest.php index f37a237..2cb619d 100644 --- a/tests/Unit/DetectorTest.php +++ b/tests/Unit/DetectorTest.php @@ -124,6 +124,34 @@ public function it_detects_no_node_configuration(): void $this->assertNull($fullConfig->nodeConfiguration); } + /** @test */ + public function it_returns_package_json_content_if_found() + { + $fullConfig = $this->sut->getFullConfiguration( + sprintf('%s/../fixtures/%s', __DIR__, 'node-configuration/full-package-json') + ); + + $this->assertNotNull($fullConfig); + $this->assertIsArray($fullConfig->nodeConfiguration->packageJsonContent); + $this->assertCount(6, array_keys($fullConfig->nodeConfiguration->packageJsonContent)); + $this->assertSame( + [ + 'private', + 'type', + 'scripts', + 'dependencies', + 'optionalDependencies', + 'devDependencies', + ], + array_keys($fullConfig->nodeConfiguration->packageJsonContent) + ); + + $this->assertCount( + 8, + $fullConfig->nodeConfiguration->packageJsonContent['dependencies'] + ); + } + /** * @test */ @@ -334,6 +362,95 @@ public function it_detects_no_extension_if_not_in_require() $this->assertSame([], $fullConfig->phpConfiguration->requiredExtensions); } + /** @test */ + public function it_returns_null_if_no_composer_json_found() + { + $fullConfig = $this->sut->getFullConfiguration( + sprintf('%s/../fixtures/%s', __DIR__, 'php-config/detector/no-composer-config') + ); + + $this->assertNotNull($fullConfig); + $this->assertNotNull($fullConfig->phpConfiguration); + $this->assertNull($fullConfig->phpConfiguration->composerJsonContent); + } + + /** @test */ + public function it_returns_composer_json_content_if_found() + { + $fullConfig = $this->sut->getFullConfiguration( + sprintf('%s/../fixtures/%s', __DIR__, 'composer-config/composer-json') + ); + + $this->assertNotNull($fullConfig); + $this->assertNotNull($fullConfig->phpConfiguration); + $this->assertIsArray($fullConfig->phpConfiguration->composerJsonContent); + $this->assertCount(13, array_keys($fullConfig->phpConfiguration->composerJsonContent)); + $this->assertSame( + [ + 'type', + 'license', + 'minimum-stability', + 'prefer-stable', + 'require', + 'require-dev', + 'config', + 'autoload', + 'autoload-dev', + 'replace', + 'scripts', + 'conflict', + 'extra', + ], + array_keys($fullConfig->phpConfiguration->composerJsonContent) + ); + } + + /** @test */ + public function it_returns_null_if_no_composer_lock_found() + { + $fullConfig = $this->sut->getFullConfiguration( + sprintf('%s/../fixtures/%s', __DIR__, 'php-config/detector/no-composer-config') + ); + + $this->assertNotNull($fullConfig); + $this->assertNotNull($fullConfig->phpConfiguration); + $this->assertNull($fullConfig->phpConfiguration->composerLockContent); + } + + /** @test */ + public function it_returns_composer_lock_content_if_both_composer_config_files_are_found() + { + $fullConfig = $this->sut->getFullConfiguration( + sprintf('%s/../fixtures/%s', __DIR__, 'composer-config/composer-both') + ); + + $this->assertNotNull($fullConfig); + $this->assertNotNull($fullConfig->phpConfiguration); + $this->assertIsArray($fullConfig->phpConfiguration->composerLockContent); + $this->assertCount(11, array_keys($fullConfig->phpConfiguration->composerLockContent)); + $this->assertSame( + [ + '_readme', + 'content-hash', + 'packages', + 'aliases', + 'minimum-stability', + 'stability-flags', + 'prefer-stable', + 'prefer-lowest', + 'platform', + 'platform-dev', + 'plugin-api-version', + ], + array_keys($fullConfig->phpConfiguration->composerLockContent) + ); + + $this->assertCount( + 3, + $fullConfig->phpConfiguration->composerLockContent['packages'] + ); + } + public static function packagesDataProvider(): array { return [ diff --git a/tests/fixtures/composer-config/composer-both/composer.json b/tests/fixtures/composer-config/composer-both/composer.json new file mode 100644 index 0000000..01153f7 --- /dev/null +++ b/tests/fixtures/composer-config/composer-both/composer.json @@ -0,0 +1,3 @@ +{ + "name": "test/package" +} diff --git a/tests/fixtures/composer-config/composer-both/composer.lock b/tests/fixtures/composer-config/composer-both/composer.lock new file mode 100644 index 0000000..4b3dcbf --- /dev/null +++ b/tests/fixtures/composer-config/composer-both/composer.lock @@ -0,0 +1,316 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0ff5bf63dfe00c079888b47286b794c9", + "packages": [ + { + "name": "someframework/foo", + "version": "v2.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "b47ca238b03e7b0d7880ffd1cf06e8d637ca1467" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/b47ca238b03e7b0d7880ffd1cf06e8d637ca1467", + "reference": "b47ca238b03e7b0d7880ffd1cf06e8d637ca1467", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-19T20:22:16+00:00" + }, + { + "name": "symfony/config", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "b47ca238b03e7b0d7880ffd1cf06e8d637ca1467" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/b47ca238b03e7b0d7880ffd1cf06e8d637ca1467", + "reference": "b47ca238b03e7b0d7880ffd1cf06e8d637ca1467", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-19T20:22:16+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "567cafcfc08e3076b47290a7558b0ca17a98b0ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/567cafcfc08e3076b47290a7558b0ca17a98b0ce", + "reference": "567cafcfc08e3076b47290a7558b0ca17a98b0ce", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.1", + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.3.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.1", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-foundation": "^6.3", + "symfony/http-kernel": "^6.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/routing": "^5.4|^6.0" + }, + "conflict": { + "doctrine/annotations": "<1.13.1", + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/asset": "<5.4", + "symfony/clock": "<6.3", + "symfony/console": "<5.4", + "symfony/dom-crawler": "<6.3", + "symfony/dotenv": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<6.3", + "symfony/lock": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<6.3", + "symfony/mime": "<6.2", + "symfony/property-access": "<5.4", + "symfony/property-info": "<5.4", + "symfony/security-core": "<5.4", + "symfony/security-csrf": "<5.4", + "symfony/serializer": "<6.3", + "symfony/stopwatch": "<5.4", + "symfony/translation": "<6.2.8", + "symfony/twig-bridge": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/validator": "<6.3", + "symfony/web-profiler-bundle": "<5.4", + "symfony/workflow": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.13.1|^2", + "doctrine/persistence": "^1.3|^2|^3", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/clock": "^6.2", + "symfony/console": "^5.4.9|^6.0.9", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dom-crawler": "^6.3", + "symfony/dotenv": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/html-sanitizer": "^6.1", + "symfony/http-client": "^6.3", + "symfony/lock": "^5.4|^6.0", + "symfony/mailer": "^5.4|^6.0", + "symfony/messenger": "^6.3", + "symfony/mime": "^6.2", + "symfony/notifier": "^5.4|^6.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/process": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/rate-limiter": "^5.4|^6.0", + "symfony/scheduler": "^6.3", + "symfony/security-bundle": "^5.4|^6.0", + "symfony/semaphore": "^5.4|^6.0", + "symfony/serializer": "^6.3", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/string": "^5.4|^6.0", + "symfony/translation": "^6.2.8", + "symfony/twig-bundle": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^6.3", + "symfony/web-link": "^5.4|^6.0", + "symfony/workflow": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0", + "twig/twig": "^2.10|^3.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\FrameworkBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/framework-bundle/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-29T10:45:15+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=8.1", + "ext-ctype": "*", + "ext-iconv": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/tests/fixtures/node-configuration/full-package-json/package.json b/tests/fixtures/node-configuration/full-package-json/package.json new file mode 100644 index 0000000..c1eb55d --- /dev/null +++ b/tests/fixtures/node-configuration/full-package-json/package.json @@ -0,0 +1,30 @@ +{ + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite" + }, + "dependencies": { + "@tailwindcss/vite": "^4.0.7", + "autoprefixer": "^10.4.20", + "axios": "^1.7.4", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^1.0", + "shiki": "^3.2.1", + "tailwindcss": "^4.1", + "vite": "^6.0" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.9.5", + "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", + "lightningcss-linux-x64-gnu": "^1.29.1" + }, + "devDependencies": { + "@alpinejs/focus": "^3.14.9", + "@fontsource-variable/ibm-plex-sans": "^5.2.5", + "@fontsource-variable/playfair-display": "^5.2.5", + "@fontsource-variable/space-grotesk": "^5.2.7", + "daisyui": "^5.0.43" + } +} diff --git a/tests/fixtures/php-config/detector/no-composer-config/.gitkeep b/tests/fixtures/php-config/detector/no-composer-config/.gitkeep new file mode 100644 index 0000000..e69de29