Browse Source

Merge branch 'master' into dead-code

Jordi Boggiano 6 years ago
parent
commit
1f10dd1aff

+ 3 - 1
.travis.yml

@@ -1,5 +1,5 @@
 language: php
-
+dist: xenial
 sudo: false
 
 env:
@@ -12,6 +12,8 @@ cache:
 
 services:
   - redis-server
+  - mysql
+
 
 php:
   - 7.0

+ 1 - 1
src/Packagist/WebBundle/Controller/AboutController.php

@@ -13,8 +13,8 @@
 namespace Packagist\WebBundle\Controller;
 
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
 use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\Routing\Annotation\Route;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>

+ 11 - 27
src/Packagist/WebBundle/Controller/ApiController.php

@@ -12,23 +12,13 @@
 
 namespace Packagist\WebBundle\Controller;
 
-use Composer\Console\HtmlOutputFormatter;
-use Composer\Factory;
-use Composer\IO\BufferIO;
-use Composer\Package\Loader\ArrayLoader;
-use Composer\Package\Loader\ValidatingArrayLoader;
-use Composer\Repository\InvalidRepositoryException;
-use Composer\Repository\VcsRepository;
 use Packagist\WebBundle\Entity\Package;
 use Packagist\WebBundle\Entity\User;
-use Packagist\WebBundle\Entity\Job;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
-use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
 use Symfony\Component\Security\Core\Exception\AccessDeniedException;
 
 /**
@@ -37,8 +27,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
 class ApiController extends Controller
 {
     /**
-     * @Route("/packages.json", name="packages", defaults={"_format" = "json"})
-     * @Method({"GET"})
+     * @Route("/packages.json", name="packages", defaults={"_format" = "json"}, methods={"GET"})
      */
     public function packagesAction()
     {
@@ -58,8 +47,7 @@ class ApiController extends Controller
     }
 
     /**
-     * @Route("/api/create-package", name="generic_create", defaults={"_format" = "json"})
-     * @Method({"POST"})
+     * @Route("/api/create-package", name="generic_create", defaults={"_format" = "json"}, methods={"POST"})
      */
     public function createPackageAction(Request $request)
     {
@@ -95,10 +83,9 @@ class ApiController extends Controller
     }
 
     /**
-     * @Route("/api/update-package", name="generic_postreceive", defaults={"_format" = "json"})
-     * @Route("/api/github", name="github_postreceive", defaults={"_format" = "json"})
-     * @Route("/api/bitbucket", name="bitbucket_postreceive", defaults={"_format" = "json"})
-     * @Method({"POST"})
+     * @Route("/api/update-package", name="generic_postreceive", defaults={"_format" = "json"}, methods={"POST"})
+     * @Route("/api/github", name="github_postreceive", defaults={"_format" = "json"}, methods={"POST"})
+     * @Route("/api/bitbucket", name="bitbucket_postreceive", defaults={"_format" = "json"}, methods={"POST"})
      */
     public function updatePackageAction(Request $request)
     {
@@ -137,10 +124,10 @@ class ApiController extends Controller
      *     "/api/packages/{package}",
      *     name="api_edit_package",
      *     requirements={"package"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?"},
-     *     defaults={"_format" = "json"}
+     *     defaults={"_format" = "json"},
+     *     methods={"PUT"}
      * )
      * @ParamConverter("package", options={"mapping": {"package": "name"}})
-     * @Method({"PUT"})
      */
     public function editPackageAction(Request $request, Package $package)
     {
@@ -174,8 +161,7 @@ class ApiController extends Controller
     }
 
     /**
-     * @Route("/downloads/{name}", name="track_download", requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+"}, defaults={"_format" = "json"})
-     * @Method({"POST"})
+     * @Route("/downloads/{name}", name="track_download", requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+"}, defaults={"_format" = "json"}, methods={"POST"})
      */
     public function trackDownloadAction(Request $request, $name)
     {
@@ -191,8 +177,7 @@ class ApiController extends Controller
     }
 
     /**
-     * @Route("/jobs/{id}", name="get_job", requirements={"id"="[a-f0-9]+"}, defaults={"_format" = "json"})
-     * @Method({"GET"})
+     * @Route("/jobs/{id}", name="get_job", requirements={"id"="[a-f0-9]+"}, defaults={"_format" = "json"}, methods={"GET"})
      */
     public function getJobAction(string $id)
     {
@@ -211,8 +196,7 @@ class ApiController extends Controller
      *
      * The version must be the normalized one
      *
-     * @Route("/downloads/", name="track_download_batch", defaults={"_format" = "json"})
-     * @Method({"POST"})
+     * @Route("/downloads/", name="track_download_batch", defaults={"_format" = "json"}, methods={"POST"})
      */
     public function trackDownloadsAction(Request $request)
     {

+ 1 - 1
src/Packagist/WebBundle/Controller/ApiDocController.php

@@ -13,7 +13,7 @@
 namespace Packagist\WebBundle\Controller;
 
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+use Symfony\Component\Routing\Annotation\Route;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be> 

+ 1 - 1
src/Packagist/WebBundle/Controller/ExploreController.php

@@ -19,10 +19,10 @@ use Packagist\WebBundle\Entity\VersionRepository;
 use Pagerfanta\Adapter\FixedAdapter;
 use Pagerfanta\Pagerfanta;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Annotation\Route;
 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
 
 /**

+ 9 - 12
src/Packagist/WebBundle/Controller/FeedController.php

@@ -15,13 +15,10 @@ namespace Packagist\WebBundle\Controller;
 use Doctrine\ORM\QueryBuilder;
 use Packagist\WebBundle\Entity\Package;
 use Packagist\WebBundle\Entity\Version;
-use Pagerfanta\Adapter\DoctrineORMAdapter;
-use Pagerfanta\Pagerfanta;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
 use Zend\Feed\Writer\Entry;
 use Zend\Feed\Writer\Feed;
@@ -46,9 +43,9 @@ class FeedController extends Controller
      * @Route(
      *     "/packages.{_format}",
      *     name="feed_packages",
-     *     requirements={"_format"="(rss|atom)"}
+     *     requirements={"_format"="(rss|atom)"},
+     *     methods={"GET"}
      * )
-     * @Method({"GET"})
      */
     public function packagesAction(Request $req)
     {
@@ -73,9 +70,9 @@ class FeedController extends Controller
      * @Route(
      *     "/releases.{_format}",
      *     name="feed_releases",
-     *     requirements={"_format"="(rss|atom)"}
+     *     requirements={"_format"="(rss|atom)"},
+     *     methods={"GET"}
      * )
-     * @Method({"GET"})
      */
     public function releasesAction(Request $req)
     {
@@ -100,9 +97,9 @@ class FeedController extends Controller
      * @Route(
      *     "/vendor.{vendor}.{_format}",
      *     name="feed_vendor",
-     *     requirements={"_format"="(rss|atom)", "vendor"="[A-Za-z0-9_.-]+"}
+     *     requirements={"_format"="(rss|atom)", "vendor"="[A-Za-z0-9_.-]+"},
+     *     methods={"GET"}
      * )
-     * @Method({"GET"})
      */
     public function vendorAction(Request $req, $vendor)
     {
@@ -127,9 +124,9 @@ class FeedController extends Controller
      * @Route(
      *     "/package.{package}.{_format}",
      *     name="feed_package",
-     *     requirements={"_format"="(rss|atom)", "package"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+"}
+     *     requirements={"_format"="(rss|atom)", "package"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+"},
+     *     methods={"GET"}
      * )
-     * @Method({"GET"})
      */
     public function packageAction(Request $req, $package)
     {

+ 1 - 1
src/Packagist/WebBundle/Controller/MirrorsController.php

@@ -13,7 +13,7 @@
 namespace Packagist\WebBundle\Controller;
 
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+use Symfony\Component\Routing\Annotation\Route;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>

+ 31 - 43
src/Packagist/WebBundle/Controller/PackageController.php

@@ -2,44 +2,35 @@
 
 namespace Packagist\WebBundle\Controller;
 
-use Composer\Factory;
-use Composer\IO\BufferIO;
-use Symfony\Component\Console\Output\OutputInterface;
-use Composer\Package\Loader\ArrayLoader;
-use Composer\Package\Loader\ValidatingArrayLoader;
-use Composer\Console\HtmlOutputFormatter;
-use Composer\Repository\VcsRepository;
+use Composer\Package\Version\VersionParser;
+use DateTimeImmutable;
 use Doctrine\ORM\NoResultException;
+use Packagist\WebBundle\Entity\Download;
+use Packagist\WebBundle\Entity\Package;
 use Packagist\WebBundle\Entity\PackageRepository;
+use Packagist\WebBundle\Entity\Version;
 use Packagist\WebBundle\Entity\VersionRepository;
-use Packagist\WebBundle\Entity\Download;
 use Packagist\WebBundle\Form\Model\MaintainerRequest;
 use Packagist\WebBundle\Form\Type\AbandonedType;
-use Packagist\WebBundle\Entity\Package;
-use Packagist\WebBundle\Entity\Version;
 use Packagist\WebBundle\Form\Type\AddMaintainerRequestType;
 use Packagist\WebBundle\Form\Type\PackageType;
 use Packagist\WebBundle\Form\Type\RemoveMaintainerRequestType;
+use Pagerfanta\Adapter\FixedAdapter;
+use Pagerfanta\Pagerfanta;
 use Predis\Connection\ConnectionException;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
 use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
-use Symfony\Component\Security\Core\Exception\AccessDeniedException;
-use Composer\Package\Version\VersionParser;
+use Symfony\Component\Routing\Annotation\Route;
 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
-use DateTimeImmutable;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
-use Pagerfanta\Adapter\FixedAdapter;
-use Pagerfanta\Pagerfanta;
-use Packagist\WebBundle\Package\Updater;
+use Symfony\Component\Security\Core\Exception\AccessDeniedException;
 
 class PackageController extends Controller
 {
@@ -52,8 +43,7 @@ class PackageController extends Controller
     }
 
     /**
-     * @Route("/packages/list.json", name="list", defaults={"_format"="json"})
-     * @Method({"GET"})
+     * @Route("/packages/list.json", name="list", defaults={"_format"="json"}, methods={"GET"})
      * @Cache(smaxage=300)
      */
     public function listAction(Request $req)
@@ -83,8 +73,7 @@ class PackageController extends Controller
     }
 
     /**
-     * @Route("/packages/updated.json", name="updated_packages", defaults={"_format"="json"})
-     * @Method({"GET"})
+     * @Route("/packages/updated.json", name="updated_packages", defaults={"_format"="json"}, methods={"GET"})
      */
     public function updatedSinceAction(Request $req)
     {
@@ -237,15 +226,16 @@ class PackageController extends Controller
      *     "/p/{name}.{_format}",
      *     name="view_package_alias",
      *     requirements={"name"="[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+?)?", "_format"="(json)"},
-     *     defaults={"_format"="html"}
+     *     defaults={"_format"="html"},
+     *     methods={"GET"}
      * )
      * @Route(
      *     "/packages/{name}",
      *     name="view_package_alias2",
      *     requirements={"name"="[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+?)?/"},
-     *     defaults={"_format"="html"}
+     *     defaults={"_format"="html"},
+     *     methods={"GET"}
      * )
-     * @Method({"GET"})
      */
     public function viewPackageAliasAction(Request $req, $name)
     {
@@ -268,9 +258,9 @@ class PackageController extends Controller
      *     "/providers/{name}",
      *     name="view_providers",
      *     requirements={"name"="[A-Za-z0-9/_.-]+?"},
-     *     defaults={"_format"="html"}
+     *     defaults={"_format"="html"},
+     *     methods={"GET"}
      * )
-     * @Method({"GET"})
      */
     public function viewProvidersAction($name)
     {
@@ -315,9 +305,9 @@ class PackageController extends Controller
      *     "/packages/{name}.{_format}",
      *     name="view_package",
      *     requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?", "_format"="(json)"},
-     *     defaults={"_format"="html"}
+     *     defaults={"_format"="html"},
+     *     methods={"GET"}
      * )
-     * @Method({"GET"})
      */
     public function viewPackageAction(Request $req, $name)
     {
@@ -441,9 +431,9 @@ class PackageController extends Controller
      * @Route(
      *     "/packages/{name}/downloads.{_format}",
      *     name="package_downloads_full",
-     *     requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?", "_format"="(json)"}
+     *     requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?", "_format"="(json)"},
+     *     methods={"GET"}
      * )
-     * @Method({"GET"})
      */
     public function viewPackageDownloadsAction(Request $req, $name)
     {
@@ -496,9 +486,9 @@ class PackageController extends Controller
      * @Route(
      *     "/versions/{versionId}.{_format}",
      *     name="view_version",
-     *     requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?", "versionId"="[0-9]+", "_format"="(json)"}
+     *     requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?", "versionId"="[0-9]+", "_format"="(json)"},
+     *     methods={"GET"}
      * )
-     * @Method({"GET"})
      */
     public function viewPackageVersionAction(Request $req, $versionId)
     {
@@ -519,9 +509,9 @@ class PackageController extends Controller
      * @Route(
      *     "/versions/{versionId}/delete",
      *     name="delete_version",
-     *     requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?", "versionId"="[0-9]+"}
+     *     requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?", "versionId"="[0-9]+"},
+     *     methods={"DELETE"}
      * )
-     * @Method({"DELETE"})
      */
     public function deletePackageVersionAction(Request $req, $versionId)
     {
@@ -548,8 +538,7 @@ class PackageController extends Controller
     }
 
     /**
-     * @Route("/packages/{name}", name="update_package", requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+"}, defaults={"_format" = "json"})
-     * @Method({"PUT"})
+     * @Route("/packages/{name}", name="update_package", requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+"}, defaults={"_format" = "json"}, methods={"PUT"})
      */
     public function updatePackageAction(Request $req, $name)
     {
@@ -607,8 +596,7 @@ class PackageController extends Controller
     }
 
     /**
-     * @Route("/packages/{name}", name="delete_package", requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+"})
-     * @Method({"DELETE"})
+     * @Route("/packages/{name}", name="delete_package", requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+"}, methods={"DELETE"})
      */
     public function deletePackageAction(Request $req, $name)
     {

+ 6 - 11
src/Packagist/WebBundle/Controller/UserController.php

@@ -14,20 +14,19 @@ namespace Packagist\WebBundle\Controller;
 
 use Doctrine\ORM\NoResultException;
 use FOS\UserBundle\Model\UserInterface;
+use Packagist\WebBundle\Entity\Job;
 use Packagist\WebBundle\Entity\Package;
 use Packagist\WebBundle\Entity\User;
-use Packagist\WebBundle\Entity\Job;
 use Packagist\WebBundle\Entity\VersionRepository;
 use Packagist\WebBundle\Model\RedisAdapter;
 use Pagerfanta\Adapter\DoctrineORMAdapter;
 use Pagerfanta\Pagerfanta;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\Routing\Annotation\Route;
 use Symfony\Component\Security\Core\Exception\AccessDeniedException;
 
 /**
@@ -83,9 +82,8 @@ class UserController extends Controller
     }
 
     /**
-     * @Route("/spammers/{name}/", name="mark_spammer")
+     * @Route("/spammers/{name}/", name="mark_spammer", methods={"POST"})
      * @ParamConverter("user", options={"mapping": {"name": "username"}})
-     * @Method({"POST"})
      */
     public function markSpammerAction(Request $req, User $user)
     {
@@ -205,9 +203,8 @@ class UserController extends Controller
 
     /**
      * @Template()
-     * @Route("/users/{name}/favorites/", name="user_favorites")
+     * @Route("/users/{name}/favorites/", name="user_favorites", methods={"GET"})
      * @ParamConverter("user", options={"mapping": {"name": "username"}})
-     * @Method({"GET"})
      */
     public function favoritesAction(Request $req, User $user)
     {
@@ -233,9 +230,8 @@ class UserController extends Controller
     }
 
     /**
-     * @Route("/users/{name}/favorites/", name="user_add_fav", defaults={"_format" = "json"})
+     * @Route("/users/{name}/favorites/", name="user_add_fav", defaults={"_format" = "json"}, methods={"POST"})
      * @ParamConverter("user", options={"mapping": {"name": "username"}})
-     * @Method({"POST"})
      */
     public function postFavoriteAction(Request $req, User $user)
     {
@@ -258,10 +254,9 @@ class UserController extends Controller
     }
 
     /**
-     * @Route("/users/{name}/favorites/{package}", name="user_remove_fav", defaults={"_format" = "json"}, requirements={"package"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?"})
+     * @Route("/users/{name}/favorites/{package}", name="user_remove_fav", defaults={"_format" = "json"}, requirements={"package"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?"}, methods={"DELETE"})
      * @ParamConverter("user", options={"mapping": {"name": "username"}})
      * @ParamConverter("package", options={"mapping": {"package": "name"}})
-     * @Method({"DELETE"})
      */
     public function deleteFavoriteAction(User $user, Package $package)
     {

+ 3 - 5
src/Packagist/WebBundle/Controller/WebController.php

@@ -17,10 +17,9 @@ use Packagist\WebBundle\Form\Type\SearchQueryType;
 use Predis\Connection\ConnectionException;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Annotation\Route;
 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
 
 /**
@@ -58,9 +57,8 @@ class WebController extends Controller
     }
 
     /**
-     * @Route("/search/", name="search.ajax")
-     * @Route("/search.{_format}", requirements={"_format"="(html|json)"}, name="search", defaults={"_format"="html"})
-     * @Method({"GET"})
+     * @Route("/search/", name="search.ajax", methods={"GET"})
+     * @Route("/search.{_format}", requirements={"_format"="(html|json)"}, name="search", defaults={"_format"="html"}, methods={"GET"})
      */
     public function searchAction(Request $req)
     {

+ 4 - 0
src/Packagist/WebBundle/Entity/Package.php

@@ -905,6 +905,10 @@ class Package
 
         // equal versions are sorted by date
         if ($aVersion === $bVersion) {
+            // make sure sort is stable
+            if ($a->getReleasedAt() === $a->getReleasedAt()) {
+                return $a->getNormalizedVersion() <=> $b->getNormalizedVersion();
+            }
             return $b->getReleasedAt() > $a->getReleasedAt() ? 1 : -1;
         }
 

+ 32 - 19
src/Packagist/WebBundle/Package/SymlinkDumper.php

@@ -701,37 +701,50 @@ class SymlinkDumper
 
     private function dumpPackageToV2File(Package $package, $versionData, string $packageKey)
     {
-        $deduplicatedVersions = [];
-        $uniqKeys = ['version', 'version_normalized', 'source', 'dist', 'time'];
+        $minifiedVersions = [];
         $mtime = 0;
 
-        foreach ($package->getVersions() as $version) {
+        $versions = $package->getVersions();
+        if (is_object($versions)) {
+            $versions = $versions->toArray();
+        }
+
+        usort($versions, Package::class.'::sortVersions');
+
+        $lastKnownVersionData = null;
+        foreach ($versions as $version) {
             $versionArray = $version->toV2Array($versionData);
+            $mtime = max($mtime, $version->getReleasedAt() ? $version->getReleasedAt()->getTimestamp() : time());
 
-            $filtered = $versionArray;
-            foreach ($uniqKeys as $key) {
-                unset($filtered[$key]);
+            if (!$lastKnownVersionData) {
+                $lastKnownVersionData = $versionArray;
+                $minifiedVersions[] = $versionArray;
+                continue;
             }
-            $hash = md5(json_encode($filtered));
 
-            if (isset($deduplicatedVersions[$hash])) {
-                foreach ($uniqKeys as $key) {
-                    $deduplicatedVersions[$hash][$key.'s'][] = $versionArray[$key] ?? null;
+            $minifiedVersion = [];
+
+            // add any changes from the previous version
+            foreach ($versionArray as $key => $val) {
+                if (!isset($lastKnownVersionData[$key]) || $lastKnownVersionData[$key] !== $val) {
+                    $minifiedVersion[$key] = $val;
+                    $lastKnownVersionData[$key] = $val;
                 }
-            } else {
-                foreach ($uniqKeys as $key) {
-                    $filtered[$key.'s'] = [$versionArray[$key] ?? null];
+            }
+
+            // store any deletions from the previous version for keys missing in current one
+            foreach ($lastKnownVersionData as $key => $val) {
+                if (!isset($versionArray[$key])) {
+                    $minifiedVersion[$key] = "__unset";
+                    unset($lastKnownVersionData[$key]);
                 }
-                $deduplicatedVersions[$hash] = $filtered;
             }
 
-            $mtime = max($mtime, $version->getReleasedAt() ? $version->getReleasedAt()->getTimestamp() : time());
+            $minifiedVersions[] = $minifiedVersion;
         }
 
-        // get rid of md5 hash keys
-        $deduplicatedVersions = array_values($deduplicatedVersions);
-
-        $this->individualFilesV2[$packageKey]['packages'][strtolower($package->getName())] = $deduplicatedVersions;
+        $this->individualFilesV2[$packageKey]['packages'][strtolower($package->getName())] = $minifiedVersions;
+        $this->individualFilesV2[$packageKey]['minified'] = 'composer/2.0';
         $this->individualFilesV2Mtime[$packageKey] = $mtime;
     }