44
55use CodedMonkey \Dirigent \Doctrine \Repository \PackageRepository ;
66use CodedMonkey \Dirigent \Validator \UniquePackage ;
7+ use Composer \Package \Version \VersionParser ;
78use Composer \Pcre \Preg ;
89use Doctrine \Common \Collections \ArrayCollection ;
910use Doctrine \Common \Collections \Collection ;
@@ -91,6 +92,8 @@ class Package
9192 */
9293 private array $ cachedVersions ;
9394
95+ private array $ sortedVersions ;
96+
9497 public function __construct ()
9598 {
9699 $ this ->installations = new PackageInstallations ($ this );
@@ -376,18 +379,30 @@ public function getPrettyBrowsableRepositoryUrl(): ?string
376379 }
377380
378381 /**
379- * Returns the default branch or latest version of the package.
382+ * @return Version[]
383+ */
384+ public function getSortedVersions (): array
385+ {
386+ if (!isset ($ this ->sortedVersions )) {
387+ $ this ->sortedVersions = $ this ->versions ->toArray ();
388+
389+ usort ($ this ->sortedVersions , [static ::class, 'sortVersions ' ]);
390+ }
391+
392+ return $ this ->sortedVersions ;
393+ }
394+
395+ /**
396+ * Returns the default branch or the latest version of the package.
380397 */
381398 public function getDefaultVersion (): ?Version
382399 {
383- $ versions = $ this ->versions -> toArray ();
400+ $ versions = $ this ->getSortedVersions ();
384401
385402 if (!count ($ versions )) {
386403 return null ;
387404 }
388405
389- usort ($ versions , [static ::class, 'sortVersions ' ]);
390-
391406 $ latestVersion = reset ($ versions );
392407 foreach ($ versions as $ version ) {
393408 if ($ version ->isDefaultBranch ()) {
@@ -399,18 +414,17 @@ public function getDefaultVersion(): ?Version
399414 }
400415
401416 /**
402- * Returns the latest (numbered) version of the package, or the default version if no versions were found.
417+ * The latest (numbered) version of the package, or the default version if no versions were found.
403418 */
404419 public function getLatestVersion (): ?Version
405420 {
406- $ versions = $ this ->versions -> toArray ();
421+ $ versions = $ this ->getSortedVersions ();
407422
408423 if (!count ($ versions )) {
409424 return null ;
410425 }
411426
412- usort ($ versions , [static ::class, 'sortVersions ' ]);
413-
427+ // Return the first non-development version
414428 foreach ($ versions as $ version ) {
415429 if (!$ version ->isDevelopment ()) {
416430 return $ version ;
@@ -420,6 +434,133 @@ public function getLatestVersion(): ?Version
420434 return $ this ->getDefaultVersion ();
421435 }
422436
437+ /**
438+ * The latest version of each major version.
439+ *
440+ * @return Version[]
441+ */
442+ public function getActiveVersions (): array
443+ {
444+ $ activeVersions = [];
445+ $ activePrereleaseVersions = [];
446+
447+ foreach ($ this ->getSortedVersions () as $ version ) {
448+ if ('stable ' !== VersionParser::parseStability ($ version ->getNormalizedVersion ())) {
449+ continue ;
450+ }
451+
452+ [$ majorVersion , $ minorVersion ] = explode ('. ' , $ version ->getNormalizedVersion ());
453+
454+ if ('0 ' === $ majorVersion ) {
455+ $ prereleaseVersion = "$ majorVersion. $ minorVersion " ;
456+
457+ $ activePrereleaseVersions [$ prereleaseVersion ] ??= $ version ;
458+ if (version_compare ($ version ->getNormalizedVersion (), $ activePrereleaseVersions [$ prereleaseVersion ]->getNormalizedVersion (), '> ' )) {
459+ $ activePrereleaseVersions [$ prereleaseVersion ] = $ version ;
460+ }
461+
462+ continue ;
463+ }
464+
465+ $ activeVersions [$ majorVersion ] ??= $ version ;
466+ if (version_compare ($ version ->getNormalizedVersion (), $ activeVersions [$ majorVersion ]->getNormalizedVersion (), '> ' )) {
467+ $ activeVersions [$ majorVersion ] = $ version ;
468+ }
469+ }
470+
471+ $ activeDevelopmentVersions = [];
472+ $ activePrereleaseDevelopmentVersions = [];
473+
474+ // Find newer unstable releases of active versions
475+ foreach ($ this ->getSortedVersions () as $ version ) {
476+ if (in_array (VersionParser::parseStability ($ version ->getNormalizedVersion ()), ['stable ' , 'dev ' ], true )) {
477+ continue ;
478+ }
479+
480+ [$ majorVersion , $ minorVersion ] = explode ('. ' , $ version ->getNormalizedVersion ());
481+
482+ $ developmentVersion = "$ majorVersion. $ minorVersion " ;
483+
484+ if ('0 ' === $ majorVersion ) {
485+ if (isset ($ activePrereleaseVersions [$ developmentVersion ]) && !version_compare ($ version ->getNormalizedVersion (), $ activePrereleaseVersions [$ developmentVersion ]->getNormalizedVersion (), '> ' )) {
486+ continue ;
487+ }
488+
489+ $ activePrereleaseDevelopmentVersions [$ developmentVersion ] ??= $ version ;
490+ if (version_compare ($ version ->getNormalizedVersion (), $ activePrereleaseDevelopmentVersions [$ developmentVersion ]->getNormalizedVersion (), '> ' )) {
491+ $ activePrereleaseDevelopmentVersions [$ developmentVersion ] = $ version ;
492+ }
493+
494+ continue ;
495+ }
496+
497+ if (isset ($ activeVersions [$ majorVersion ]) && !version_compare ($ version ->getNormalizedVersion (), $ activeVersions [$ majorVersion ]->getNormalizedVersion (), '> ' )) {
498+ continue ;
499+ }
500+
501+ $ activeDevelopmentVersions [$ developmentVersion ] ??= $ version ;
502+ if (version_compare ($ version ->getNormalizedVersion (), $ activeDevelopmentVersions [$ developmentVersion ]->getNormalizedVersion (), '> ' )) {
503+ $ activeDevelopmentVersions [$ version ->getNormalizedVersion ()] = $ version ;
504+ }
505+ }
506+
507+ $ activeVersions = [...$ activeVersions , ...$ activeDevelopmentVersions ];
508+
509+ if (count ($ activeVersions )) {
510+ usort ($ activeVersions , [static ::class, 'sortVersions ' ]);
511+
512+ return $ activeVersions ;
513+ }
514+
515+ // Only show pre-release versions (0.x.x) if no versions after 1.0.0 was found
516+ $ activePrereleaseVersions = [...$ activePrereleaseVersions , ...$ activePrereleaseDevelopmentVersions ];
517+
518+ usort ($ activePrereleaseVersions , [static ::class, 'sortVersions ' ]);
519+
520+ return $ activePrereleaseVersions ;
521+ }
522+
523+ /**
524+ * All non-development versions that are not part of the active versions.
525+ *
526+ * @return Version[]
527+ */
528+ public function getHistoricalVersions (): array
529+ {
530+ $ historicalVersions = array_filter ($ this ->getSortedVersions (), static fn (Version $ version ) => !$ version ->isDevelopment ());
531+
532+ return array_diff ($ historicalVersions , $ this ->getActiveVersions ());
533+ }
534+
535+ /**
536+ * All development versions associated with a version number (2.0.x-dev, 0.1.x-dev).
537+ *
538+ * @return Version[]
539+ */
540+ public function getDevVersions (): array
541+ {
542+ $ parser = new VersionParser ();
543+
544+ return array_filter ($ this ->getSortedVersions (), function (Version $ version ) use ($ parser ) {
545+ if (str_ends_with ($ version ->getNormalizedVersion (), '.9999999-dev ' )) {
546+ return true ;
547+ }
548+
549+
550+ return $ version ->hasVersionAlias () && str_ends_with ($ parser ->normalize ($ version ->getVersionAlias ()), '.9999999-dev ' );
551+ });
552+ }
553+
554+ /**
555+ * All development versions associated with a branch (dev-main, dev-master, dev-develop).
556+ *
557+ * @return Version[]
558+ */
559+ public function getDevBranchVersions (): array
560+ {
561+ return array_filter ($ this ->getSortedVersions (), static fn (Version $ version ) => str_starts_with ($ version ->getNormalizedVersion (), 'dev- ' ));
562+ }
563+
423564 public static function sortVersions (Version $ a , Version $ b ): int
424565 {
425566 $ aVersion = $ a ->getNormalizedVersion ();
0 commit comments