diff --git a/.github/workflows/package-tests.yml b/.github/workflows/package-tests.yml new file mode 100644 index 0000000..28785e1 --- /dev/null +++ b/.github/workflows/package-tests.yml @@ -0,0 +1,54 @@ +name: Package Tests + +on: + schedule: + - cron: '0 0 * * *' + pull_request: + push: + branches: + - 1.x + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + tests: + name: Package Tests + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + coverage: none + + - name: Get composer cache directory + id: cache-dir + shell: bash + run: | + echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_OUTPUT + + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.cache-dir.outputs.COMPOSER_CACHE_DIR }} + key: ${{ github.workflow }}-PHP_8.3-${{ hashFiles('**/composer.json') }} + restore-keys: | + ${{ github.workflow }}-PHP_8.3- + + - name: Install dependencies + run: composer update --ansi --no-scripts + + - name: Run Package Tests + run: | + vendor/bin/phpunit --color=always --group=package-test --no-coverage + env: + TACHYCARDIA_MONITOR_GA: enabled diff --git a/phpstan-baseline.php b/phpstan-baseline.php index c3212fb..7caf258 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -7,5 +7,11 @@ 'count' => 1, 'path' => __DIR__ . '/src/Nexus/Option/Choice.php', ]; +$ignoreErrors[] = [ + // identifier: return.type + 'message' => '#^Method Nexus\\\\Tests\\\\AutoReview\\\\ComposerJsonTest\\:\\:getComposer\\(\\) should return array\\ but returns mixed\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/tests/AutoReview/ComposerJsonTest.php', +]; return ['parameters' => ['ignoreErrors' => $ignoreErrors]]; diff --git a/src/Nexus/Clock/composer.json b/src/Nexus/Clock/composer.json index 6b9befb..df55a3a 100644 --- a/src/Nexus/Clock/composer.json +++ b/src/Nexus/Clock/composer.json @@ -26,6 +26,7 @@ "psr/clock-implementation": "1.0" }, "minimum-stability": "dev", + "prefer-stable": true, "autoload": { "psr-4": { "Nexus\\Clock\\": "" diff --git a/src/Nexus/Option/composer.json b/src/Nexus/Option/composer.json index 697d346..7ae002d 100644 --- a/src/Nexus/Option/composer.json +++ b/src/Nexus/Option/composer.json @@ -21,6 +21,7 @@ "php": "^8.2" }, "minimum-stability": "dev", + "prefer-stable": true, "autoload": { "psr-4": { "Nexus\\Option\\": "" diff --git a/tests/AutoReview/ComposerJsonTest.php b/tests/AutoReview/ComposerJsonTest.php new file mode 100644 index 0000000..9a210f1 --- /dev/null +++ b/tests/AutoReview/ComposerJsonTest.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Nexus\Tests\AutoReview; + +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\TestCase; + +/** + * @internal + */ +#[CoversNothing] +#[Group('package-test')] +final class ComposerJsonTest extends TestCase +{ + /** + * @var list + */ + private static array $packages = []; + + public function testRootComposerJsonReplacesPackages(): void + { + $rootComposer = $this->getComposer(__DIR__.'/../../composer.json'); + + self::assertArrayHasKey('replace', $rootComposer); + self::assertIsArray($rootComposer['replace']); + + foreach (self::getPackageDirectories() as $directory) { + $package = $this->getPackageName($directory); + + self::assertArrayHasKey($package, $rootComposer['replace']); + self::assertSame('self.version', $rootComposer['replace'][$package]); + } + } + + #[DataProvider('providePackageHasComposerJsonCases')] + public function testPackageHasComposerJson(string $package): void + { + $composerJson = \sprintf('%s/composer.json', $package); + self::assertFileExists($composerJson); + + $composerJson = $this->getComposer($composerJson); + self::assertArrayHasKey('minimum-stability', $composerJson); + self::assertSame('dev', $composerJson['minimum-stability']); + + self::assertArrayHasKey('prefer-stable', $composerJson); + self::assertTrue($composerJson['prefer-stable']); + + self::assertArrayHasKey('support', $composerJson); + self::assertIsArray($composerJson['support']); + self::assertArrayHasKey('issues', $composerJson['support']); + self::assertArrayHasKey('source', $composerJson['support']); + self::assertSame('https://github.com/NexusPHP/framework/issues', $composerJson['support']['issues']); + self::assertSame('https://github.com/NexusPHP/framework', $composerJson['support']['source']); + } + + /** + * @return iterable + */ + public static function providePackageHasComposerJsonCases(): iterable + { + foreach (self::getPackageDirectories() as $directory) { + yield $directory => [$directory]; + } + } + + /** + * @return array + */ + private function getComposer(string $path): array + { + try { + $realpath = realpath($path); + + if (false === $realpath) { + throw new \InvalidArgumentException(\sprintf( + 'The composer.json at "%s" does not exist.', + $path, + )); + } + + $contents = @file_get_contents($realpath); + + if (false === $contents) { + throw new \InvalidArgumentException(\sprintf( + 'The composer.json at "%s" is not readable.', + substr($realpath, \strlen((string) getcwd())), + )); + } + + return json_decode($contents, true, 512, JSON_THROW_ON_ERROR); + } catch (\InvalidArgumentException|\JsonException $e) { + self::fail(\sprintf('Retrieving the contents failed: %s', $e->getMessage())); + } + } + + /** + * @return list + */ + private static function getPackageDirectories(): array + { + if ([] !== self::$packages) { + return self::$packages; + } + + $finder = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator( + 'src/Nexus', + \FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::UNIX_PATHS, + ), + ); + $finder->setMaxDepth(3); + + /** + * @var \SplFileInfo $splFileInfo + */ + foreach ($finder as $file => $splFileInfo) { + if ('composer.json' === $file) { + self::$packages[] = \dirname($splFileInfo->getPathname()); + } + } + + return self::$packages; + } + + private function getPackageName(string $package): string + { + static $specialCases = [ + 'PHPStan' => 'phpstan-nexus', + ]; + + $package = basename($package); + + return \sprintf( + 'nexusphp/%s', + $specialCases[$package] ?? strtolower(preg_replace( + '/(?