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 );
@@ -368,18 +371,30 @@ public function getPrettyBrowsableRepositoryUrl(): ?string
368371 }
369372
370373 /**
371- * Returns the default branch or latest version of the package.
374+ * @return Version[]
375+ */
376+ public function getSortedVersions (): array
377+ {
378+ if (!isset ($ this ->sortedVersions )) {
379+ $ this ->sortedVersions = $ this ->versions ->toArray ();
380+
381+ usort ($ this ->sortedVersions , [static ::class, 'sortVersions ' ]);
382+ }
383+
384+ return $ this ->sortedVersions ;
385+ }
386+
387+ /**
388+ * Returns the default branch or the latest version of the package.
372389 */
373390 public function getDefaultVersion (): ?Version
374391 {
375- $ versions = $ this ->versions -> toArray ();
392+ $ versions = $ this ->getSortedVersions ();
376393
377394 if (!count ($ versions )) {
378395 return null ;
379396 }
380397
381- usort ($ versions , [static ::class, 'sortVersions ' ]);
382-
383398 $ latestVersion = reset ($ versions );
384399 foreach ($ versions as $ version ) {
385400 if ($ version ->isDefaultBranch ()) {
@@ -391,18 +406,17 @@ public function getDefaultVersion(): ?Version
391406 }
392407
393408 /**
394- * Returns the latest (numbered) version of the package, or the default version if no versions were found.
409+ * The latest (numbered) version of the package, or the default version if no versions were found.
395410 */
396411 public function getLatestVersion (): ?Version
397412 {
398- $ versions = $ this ->versions -> toArray ();
413+ $ versions = $ this ->getSortedVersions ();
399414
400415 if (!count ($ versions )) {
401416 return null ;
402417 }
403418
404- usort ($ versions , [static ::class, 'sortVersions ' ]);
405-
419+ // Return the first non-development version
406420 foreach ($ versions as $ version ) {
407421 if (!$ version ->isDevelopment ()) {
408422 return $ version ;
@@ -412,6 +426,132 @@ public function getLatestVersion(): ?Version
412426 return $ this ->getDefaultVersion ();
413427 }
414428
429+ /**
430+ * The latest version of each major version.
431+ *
432+ * @return Version[]
433+ */
434+ public function getActiveVersions (): array
435+ {
436+ $ activeVersions = [];
437+ $ activePrereleaseVersions = [];
438+
439+ foreach ($ this ->getSortedVersions () as $ version ) {
440+ if ('stable ' !== VersionParser::parseStability ($ version ->getNormalizedVersion ())) {
441+ continue ;
442+ }
443+
444+ [$ majorVersion , $ minorVersion ] = explode ('. ' , $ version ->getNormalizedVersion ());
445+
446+ if ('0 ' === $ majorVersion ) {
447+ $ prereleaseVersion = "$ majorVersion. $ minorVersion " ;
448+
449+ $ activePrereleaseVersions [$ prereleaseVersion ] ??= $ version ;
450+ if (version_compare ($ version ->getNormalizedVersion (), $ activePrereleaseVersions [$ prereleaseVersion ]->getNormalizedVersion (), '> ' )) {
451+ $ activePrereleaseVersions [$ prereleaseVersion ] = $ version ;
452+ }
453+
454+ continue ;
455+ }
456+
457+ $ activeVersions [$ majorVersion ] ??= $ version ;
458+ if (version_compare ($ version ->getNormalizedVersion (), $ activeVersions [$ majorVersion ]->getNormalizedVersion (), '> ' )) {
459+ $ activeVersions [$ majorVersion ] = $ version ;
460+ }
461+ }
462+
463+ $ activeDevelopmentVersions = [];
464+ $ activePrereleaseDevelopmentVersions = [];
465+
466+ // Find newer unstable releases of active versions
467+ foreach ($ this ->getSortedVersions () as $ version ) {
468+ if (in_array (VersionParser::parseStability ($ version ->getNormalizedVersion ()), ['stable ' , 'dev ' ], true )) {
469+ continue ;
470+ }
471+
472+ [$ majorVersion , $ minorVersion ] = explode ('. ' , $ version ->getNormalizedVersion ());
473+
474+ $ developmentVersion = "$ majorVersion. $ minorVersion " ;
475+
476+ if ('0 ' === $ majorVersion ) {
477+ if (isset ($ activePrereleaseVersions [$ developmentVersion ]) && !version_compare ($ version ->getNormalizedVersion (), $ activePrereleaseVersions [$ developmentVersion ]->getNormalizedVersion (), '> ' )) {
478+ continue ;
479+ }
480+
481+ $ activePrereleaseDevelopmentVersions [$ developmentVersion ] ??= $ version ;
482+ if (version_compare ($ version ->getNormalizedVersion (), $ activePrereleaseDevelopmentVersions [$ developmentVersion ]->getNormalizedVersion (), '> ' )) {
483+ $ activePrereleaseDevelopmentVersions [$ developmentVersion ] = $ version ;
484+ }
485+
486+ continue ;
487+ }
488+
489+ if (isset ($ activeVersions [$ majorVersion ]) && !version_compare ($ version ->getNormalizedVersion (), $ activeVersions [$ majorVersion ]->getNormalizedVersion (), '> ' )) {
490+ continue ;
491+ }
492+
493+ $ activeDevelopmentVersions [$ developmentVersion ] ??= $ version ;
494+ if (version_compare ($ version ->getNormalizedVersion (), $ activeDevelopmentVersions [$ developmentVersion ]->getNormalizedVersion (), '> ' )) {
495+ $ activeDevelopmentVersions [$ version ->getNormalizedVersion ()] = $ version ;
496+ }
497+ }
498+
499+ $ activeVersions = [...$ activeVersions , ...$ activeDevelopmentVersions ];
500+
501+ if (count ($ activeVersions )) {
502+ usort ($ activeVersions , [static ::class, 'sortVersions ' ]);
503+
504+ return $ activeVersions ;
505+ }
506+
507+ // Only show pre-release versions (0.x.x) if no versions after 1.0.0 was found
508+ $ activePrereleaseVersions = [...$ activePrereleaseVersions , ...$ activePrereleaseDevelopmentVersions ];
509+
510+ usort ($ activePrereleaseVersions , [static ::class, 'sortVersions ' ]);
511+
512+ return $ activePrereleaseVersions ;
513+ }
514+
515+ /**
516+ * All non-development versions that are not part of the active versions.
517+ *
518+ * @return Version[]
519+ */
520+ public function getHistoricalVersions (): array
521+ {
522+ $ historicalVersions = array_filter ($ this ->getSortedVersions (), static fn (Version $ version ) => !$ version ->isDevelopment ());
523+
524+ return array_diff ($ historicalVersions , $ this ->getActiveVersions ());
525+ }
526+
527+ /**
528+ * All development versions associated with a version number (2.0.x-dev, 0.1.x-dev).
529+ *
530+ * @return Version[]
531+ */
532+ public function getDevVersions (): array
533+ {
534+ return array_filter ($ this ->getSortedVersions (), static function (Version $ version ) {
535+ if (str_ends_with ($ version ->getNormalizedVersion (), '.9999999-dev ' )) {
536+ return true ;
537+ }
538+
539+ static $ parser = new VersionParser ();
540+
541+ return $ version ->hasVersionAlias () && str_ends_with ($ parser ->normalize ($ version ->getVersionAlias ()), '.9999999-dev ' );
542+ });
543+ }
544+
545+ /**
546+ * All development versions associated with a branch (dev-main, dev-master, dev-develop).
547+ *
548+ * @return Version[]
549+ */
550+ public function getDevBranchVersions (): array
551+ {
552+ return array_filter ($ this ->getSortedVersions (), static fn (Version $ version ) => str_starts_with ($ version ->getNormalizedVersion (), 'dev- ' ));
553+ }
554+
415555 public static function sortVersions (Version $ a , Version $ b ): int
416556 {
417557 $ aVersion = $ a ->getNormalizedVersion ();
0 commit comments