From 366a7c88127575eff67c99fade997ccc57fd50f7 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Mon, 6 Apr 2026 15:39:06 +0200 Subject: [PATCH] add latest stable release date info to package --- ...0_AddLatestStableReleaseDateToPackages.php | 20 ++++++++++++++ config/Migrations/schema-dump-default.lock | Bin 8377 -> 24546 bytes src/Command/SyncPackagesCommand.php | 25 ++++++++++++++++-- src/Controller/PackagesController.php | 2 +- src/Model/Entity/Package.php | 1 + src/Model/Table/PackagesTable.php | 5 ++++ templates/Packages/index.php | 1 + templates/element/Packages/package-tile.php | 3 +++ tests/Fixture/PackagesFixture.php | 2 ++ .../Command/SyncPackagesCommandTest.php | 20 ++++++++++++++ .../Controller/PackagesControllerTest.php | 1 + 11 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 config/Migrations/20260406110000_AddLatestStableReleaseDateToPackages.php diff --git a/config/Migrations/20260406110000_AddLatestStableReleaseDateToPackages.php b/config/Migrations/20260406110000_AddLatestStableReleaseDateToPackages.php new file mode 100644 index 00000000..4eb7d1bd --- /dev/null +++ b/config/Migrations/20260406110000_AddLatestStableReleaseDateToPackages.php @@ -0,0 +1,20 @@ +table('packages') + ->addColumn('latest_stable_release_date', 'date', [ + 'default' => null, + 'null' => true, + ]) + ->update(); + } +} diff --git a/config/Migrations/schema-dump-default.lock b/config/Migrations/schema-dump-default.lock index 25fbd51554b81df9de26c18f67d3c26ad267ab5c..7a7fafbb37affd04c0ebbf6daad877ea86c7545e 100644 GIT binary patch delta 1867 zcmah}U2ofD6rM>luW7R+{fJ0ASCL0IKx~T(ZPT|)S$j>zU~s{e<=Su4mpFE0I~7wm zI&GJ$CZZ!G5PyLokgvF5+#(@$6Sti-b^+pQ+aDO5*I#j)ZQPx6{Q7)6&w0+ztB>yq z8I8>Ul=-VzQs#;5+B(xrwd2^E%6VF3y{0ux2pO4guj0LutL7m?9rL^nc&$fhuGDl=h*Qx8O zp53C*%aP)dm<9K7Ah>Ynik8QG`2-L=ha{Wk9 z2P&^$^IPL9g8$FhwT!q#2>cq7=%=)0(*FTC+(FcvgJ> zEGIb?3#J}aTPQXW(ZjcDZ)cgW5I33O?}?uZ+07Q3&@NvLEew7vaH(OuZ5x#DQ?Bp& z`&m6`|?~)xxYT>qoL(0pHC>X$-71dy54*hiIk?P&NOHkJ_oW0VeH&X7)E?GS|WTW_! zp8&U`>0IWA{6wZA?v`tIoA@9$gX1D%W2Ux2!7Mk1xDg=CcSE@CRvO16b5C3o zaTy{J{)#K|?9>PUC&T3vSOV{_?F1}`7M?4V?wi6Q{3(8qVxE8-m5;byKdZk&uIWh! zbP)|W5RV4r1WrzbgYY*vVI+1qkQ%vwp|H2v{489UkvQ2gRGjL>Mxsy8#Y87nLz+_^ zLazD){9W7H17SKcu>=em OTyc{mE>E*qU5WybW)^Dz diff --git a/src/Command/SyncPackagesCommand.php b/src/Command/SyncPackagesCommand.php index 16bdfc96..377e88ca 100644 --- a/src/Command/SyncPackagesCommand.php +++ b/src/Command/SyncPackagesCommand.php @@ -7,10 +7,12 @@ use Cake\Console\Arguments; use Cake\Console\CommandFactoryInterface; use Cake\Console\ConsoleIo; +use Cake\I18n\Date; use Cake\Log\Log; use Composer\Semver\Intervals; use Composer\Semver\VersionParser; use Packagist\Api\Client; +use Packagist\Api\Result\Package\Version; use UnexpectedValueException; /** @@ -139,7 +141,7 @@ public function execute(Arguments $args, ConsoleIo $io) /** * @param string $packageName - * @return array{package: string, description: string, repo_url: string, downloads: int, stars: int, tag_list: array, latest_stable_version: ?string, is_abandoned: bool} + * @return array{package: string, description: string, repo_url: string, downloads: int, stars: int, tag_list: array, latest_stable_version: ?string, latest_stable_release_date: ?\Cake\I18n\Date, is_abandoned: bool} */ private function getDataForPackage(string $packageName): array { @@ -155,6 +157,7 @@ private function getDataForPackage(string $packageName): array 'stars' => $metaDetails->getGithubStars(), 'tag_list' => [], 'latest_stable_version' => null, + 'latest_stable_release_date' => null, 'is_abandoned' => true, ]; } @@ -183,10 +186,14 @@ private function getDataForPackage(string $packageName): array } } - $stableVersions = array_filter($versions, fn($v) => preg_match('/^v?\d+\.\d+(\.\d+)?$/', $v->getVersion())); + $stableVersions = array_filter( + $versions, + fn(Version $version) => preg_match('/^v?\d+\.\d+(\.\d+)?$/', $version->getVersion()), + ); usort($stableVersions, function ($a, $b) { return version_compare($a->getVersion(), $b->getVersion()); }); + /** @var \Packagist\Api\Result\Package\Version|false $latestStable */ $latestStable = end($stableVersions); return [ @@ -197,10 +204,24 @@ private function getDataForPackage(string $packageName): array 'stars' => $metaDetails->getGithubStars(), 'tag_list' => $meta, 'latest_stable_version' => $latestStable ? $latestStable->getVersion() : null, + 'latest_stable_release_date' => $this->extractReleaseDate($latestStable ?: null), 'is_abandoned' => $metaDetails->isAbandoned(), ]; } + /** + * @param \Packagist\Api\Result\Package\Version|null $version + * @return \Cake\I18n\Date|null + */ + private function extractReleaseDate(?Version $version): ?Date + { + if (!$version || $version->getTime() === '') { + return null; + } + + return new Date($version->getTime()); + } + /** * @param list $tags * @return bool diff --git a/src/Controller/PackagesController.php b/src/Controller/PackagesController.php index 612af8c2..03924bc6 100644 --- a/src/Controller/PackagesController.php +++ b/src/Controller/PackagesController.php @@ -35,7 +35,7 @@ public function index() if (empty($queryParams['sort'])) { $this->request = $this->request->withQueryParams(array_merge( $queryParams, - ['sort' => 'downloads', 'direction' => 'desc'], + ['sort' => 'latest_stable_release_date', 'direction' => 'desc'], )); } diff --git a/src/Model/Entity/Package.php b/src/Model/Entity/Package.php index 5fee8f15..0df2964a 100644 --- a/src/Model/Entity/Package.php +++ b/src/Model/Entity/Package.php @@ -15,6 +15,7 @@ * @property int $downloads * @property int $stars * @property string|null $latest_stable_version + * @property \Cake\I18n\Date|null $latest_stable_release_date * * @property \Tags\Model\Entity\Tag[] $tags * diff --git a/src/Model/Table/PackagesTable.php b/src/Model/Table/PackagesTable.php index 870166e4..394100e2 100644 --- a/src/Model/Table/PackagesTable.php +++ b/src/Model/Table/PackagesTable.php @@ -86,6 +86,11 @@ public function validationDefault(Validator $validator): Validator ->requirePresence('latest_stable_version', 'create') ->allowEmptyString('latest_stable_version'); + $validator + ->date('latest_stable_release_date') + ->requirePresence('latest_stable_release_date', 'create') + ->allowEmptyDate('latest_stable_release_date'); + return $validator; } } diff --git a/templates/Packages/index.php b/templates/Packages/index.php index abb170d3..20660301 100644 --- a/templates/Packages/index.php +++ b/templates/Packages/index.php @@ -115,6 +115,7 @@ class="packages index content" ?> Paginator->sort('downloads', 'Downloads') ?> Paginator->sort('stars', 'Stars') ?> + Paginator->sort('latest_stable_release_date', 'Latest Release') ?> diff --git a/templates/element/Packages/package-tile.php b/templates/element/Packages/package-tile.php index abcaa579..06df2326 100644 --- a/templates/element/Packages/package-tile.php +++ b/templates/element/Packages/package-tile.php @@ -119,6 +119,9 @@ class="btn btn-xs latest_stable_version ?: __('Unknown')) ?> +
+ latest_stable_release_date?->i18nFormat('MMM d, yyyy') ?? __('Unknown')) ?> +
diff --git a/tests/Fixture/PackagesFixture.php b/tests/Fixture/PackagesFixture.php index f355a0f0..f720d2d3 100644 --- a/tests/Fixture/PackagesFixture.php +++ b/tests/Fixture/PackagesFixture.php @@ -26,6 +26,7 @@ public function init(): void 'downloads' => 5000, 'stars' => 450, 'latest_stable_version' => '5.0.0', + 'latest_stable_release_date' => '2025-01-15', ], ]; @@ -39,6 +40,7 @@ public function init(): void 'downloads' => 1000 - $i, 'stars' => 100 - $i, 'latest_stable_version' => sprintf('1.%d.0', $i - 2), + 'latest_stable_release_date' => sprintf('2025-02-%02d', $i), ]; } diff --git a/tests/TestCase/Command/SyncPackagesCommandTest.php b/tests/TestCase/Command/SyncPackagesCommandTest.php index 7df7e955..6efa07d4 100644 --- a/tests/TestCase/Command/SyncPackagesCommandTest.php +++ b/tests/TestCase/Command/SyncPackagesCommandTest.php @@ -5,7 +5,9 @@ use App\Command\SyncPackagesCommand; use Cake\Console\TestSuite\ConsoleIntegrationTestTrait; +use Cake\I18n\Date; use Cake\TestSuite\TestCase; +use Packagist\Api\Result\Package\Version; use ReflectionMethod; /** @@ -30,6 +32,24 @@ public function testHasExplicitCakePhpDependency(): void $this->assertFalse($method->invoke($command, ['PHP: 8.2'])); } + /** + * @return void + */ + public function testExtractReleaseDate(): void + { + $command = new SyncPackagesCommand(); + $method = new ReflectionMethod($command, 'extractReleaseDate'); + $method->setAccessible(true); + + $version = new Version(); + $version->fromArray([ + 'time' => '2026-04-05T11:22:33+00:00', + ]); + + $this->assertEquals(new Date('2026-04-05'), $method->invoke($command, $version)); + $this->assertNull($method->invoke($command, null)); + } + /** * Test defaultName method * diff --git a/tests/TestCase/Controller/PackagesControllerTest.php b/tests/TestCase/Controller/PackagesControllerTest.php index 295d782b..49c7fa31 100644 --- a/tests/TestCase/Controller/PackagesControllerTest.php +++ b/tests/TestCase/Controller/PackagesControllerTest.php @@ -44,6 +44,7 @@ public function testIndexShowsFeaturedSliderOnFirstPageWithoutFilters(): void $this->assertResponseOk(); $this->assertResponseContains('Featured'); $this->assertResponseContains('data-featured-packages-slider'); + $this->assertResponseContains('Latest Release'); } /**