From e69b4946869d964727575c52a7343ec0dccf8838 Mon Sep 17 00:00:00 2001 From: Tim Goudriaan Date: Fri, 23 Aug 2024 14:18:49 +0200 Subject: [PATCH] Refactor dumping of provider files with custom logic --- src/Doctrine/Entity/Package.php | 13 ++ src/Doctrine/Entity/PackageLink.php | 8 ++ src/Doctrine/Entity/Version.php | 133 ++++++++++++++++++++- src/Message/DumpPackageProvider.php | 11 ++ src/Message/DumpPackageProviderHandler.php | 24 ++++ src/Package/PackageMetadataResolver.php | 35 +++++- src/Package/PackageProviderManager.php | 21 ++-- 7 files changed, 230 insertions(+), 15 deletions(-) create mode 100644 src/Message/DumpPackageProvider.php create mode 100644 src/Message/DumpPackageProviderHandler.php diff --git a/src/Doctrine/Entity/Package.php b/src/Doctrine/Entity/Package.php index ea4fbfc..f977e25 100644 --- a/src/Doctrine/Entity/Package.php +++ b/src/Doctrine/Entity/Package.php @@ -37,6 +37,9 @@ class Package #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $description = null; + #[ORM\Column(nullable: true)] + private ?string $type = null; + #[ORM\Column(nullable: true)] private ?string $language = null; @@ -146,6 +149,16 @@ public function setDescription(?string $description): void $this->description = $description; } + public function getType(): ?string + { + return $this->type; + } + + public function setType(?string $type): void + { + $this->type = $type; + } + public function getLanguage(): ?string { return $this->language; diff --git a/src/Doctrine/Entity/PackageLink.php b/src/Doctrine/Entity/PackageLink.php index 71b4ec7..cc8c780 100644 --- a/src/Doctrine/Entity/PackageLink.php +++ b/src/Doctrine/Entity/PackageLink.php @@ -59,4 +59,12 @@ public function setVersion(Version $version): void { $this->version = $version; } + + /** + * @return non-empty-array + */ + public function toArray(): array + { + return [$this->getPackageName() => $this->getPackageVersion()]; + } } diff --git a/src/Doctrine/Entity/Version.php b/src/Doctrine/Entity/Version.php index 7662dcd..11e068d 100644 --- a/src/Doctrine/Entity/Version.php +++ b/src/Doctrine/Entity/Version.php @@ -414,7 +414,7 @@ public function setPhpExt(?array $phpExt): void public function getAuthors(): array { - return $this->authors; + return $this->authors ?? []; } public function setAuthors(array $authors): void @@ -536,4 +536,135 @@ public function getVersionAlias(): string return ''; } + + /** + * Get funding, sorted to help the V2 metadata compression algo + * @return array|null + */ + public function getFundingSorted(): ?array + { + if ($this->funding === null) { + return null; + } + + $funding = $this->funding; + usort($funding, static function ($a, $b) { + $keyA = ($a['type'] ?? '') . ($a['url'] ?? ''); + $keyB = ($b['type'] ?? '') . ($b['url'] ?? ''); + + return $keyA <=> $keyB; + }); + + return $funding; + } + + public function toComposerArray(): array + { + $tags = []; + foreach ($this->getTags() as $tag) { + $tags[] = $tag->getName(); + } + + $authors = $this->getAuthors(); + foreach ($authors as &$author) { + uksort($author, [$this, 'sortAuthorKeys']); + } + unset($author); + + $data = [ + 'name' => $this->getName(), + 'description' => (string) $this->getDescription(), + 'keywords' => $tags, + 'homepage' => (string) $this->getHomepage(), + 'version' => $this->getVersion(), + 'version_normalized' => $this->getNormalizedVersion(), + 'license' => $this->getLicense(), + 'authors' => $authors, + 'source' => $this->getSource(), + 'dist' => $this->getDist(), + 'type' => $this->getType(), + ]; + + if ($this->getSupport()) { + $data['support'] = $this->getSupport(); + } + if ($this->getPhpExt() !== null) { + $data['php-ext'] = $this->getPhpExt(); + } + $funding = $this->getFundingSorted(); + if ($funding !== null) { + $data['funding'] = $funding; + } + if ($this->getReleasedAt()) { + $data['time'] = $this->getReleasedAt()->format('Y-m-d\TH:i:sP'); + } + if ($this->getAutoload()) { + $data['autoload'] = $this->getAutoload(); + } + if ($this->getExtra()) { + $data['extra'] = $this->getExtra(); + } + if ($this->getTargetDir()) { + $data['target-dir'] = $this->getTargetDir(); + } + if ($this->getIncludePaths()) { + $data['include-path'] = $this->getIncludePaths(); + } + if ($this->getBinaries()) { + $data['bin'] = $this->getBinaries(); + } + + $supportedLinkTypes = [ + 'require' => 'require', + 'devRequire' => 'require-dev', + 'suggest' => 'suggest', + 'conflict' => 'conflict', + 'provide' => 'provide', + 'replace' => 'replace', + ]; + + if ($this->isDefaultBranch()) { + $data['default-branch'] = true; + } + + foreach ($supportedLinkTypes as $method => $linkType) { + if (isset($versionData[$this->id][$method])) { + foreach ($versionData[$this->id][$method] as $link) { + $data[$linkType][$link['name']] = $link['version']; + } + continue; + } + /** @var PackageLink $link */ + foreach ($this->{'get'.$method}() as $link) { + $link = $link->toArray(); + $data[$linkType][key($link)] = current($link); + } + } + + if ($this->getPackage()->isAbandoned()) { + $data['abandoned'] = $this->getPackage()->getReplacementPackage() ?: true; + } + + if (isset($data['support'])) { + ksort($data['support']); + } + + if (isset($data['php-ext']['configure-options'])) { + usort($data['php-ext']['configure-options'], fn ($a, $b) => $a['name'] ?? '' <=> $b['name'] ?? ''); + } + + return $data; + } + + private function sortAuthorKeys(string $a, string $b): int + { + static $order = ['name' => 1, 'email' => 2, 'homepage' => 3, 'role' => 4]; + $aIndex = $order[$a] ?? 5; + $bIndex = $order[$b] ?? 5; + if ($aIndex === $bIndex) { + return $a <=> $b; + } + + return $aIndex <=> $bIndex; + } } diff --git a/src/Message/DumpPackageProvider.php b/src/Message/DumpPackageProvider.php new file mode 100644 index 0000000..fdbc163 --- /dev/null +++ b/src/Message/DumpPackageProvider.php @@ -0,0 +1,11 @@ +packageRepository->find($message->packageId); + + $this->providerManager->dump($package); + } +} diff --git a/src/Package/PackageMetadataResolver.php b/src/Package/PackageMetadataResolver.php index 91e479b..ee747f0 100644 --- a/src/Package/PackageMetadataResolver.php +++ b/src/Package/PackageMetadataResolver.php @@ -12,6 +12,7 @@ use CodedMonkey\Conductor\Doctrine\Entity\Version; use CodedMonkey\Conductor\Doctrine\Repository\RegistryRepository; use CodedMonkey\Conductor\Doctrine\Repository\VersionRepository; +use CodedMonkey\Conductor\Message\DumpPackageProvider; use Composer\IO\NullIO; use Composer\Package\AliasPackage; use Composer\Package\CompletePackageInterface; @@ -20,6 +21,7 @@ use Composer\Repository\VcsRepository; use Composer\Util\HttpDownloader; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Messenger\MessageBusInterface; class PackageMetadataResolver { @@ -47,7 +49,7 @@ class PackageMetadataResolver ]; public function __construct( - private readonly PackageProviderManager $providerManager, + private readonly MessageBusInterface $messenger, private readonly EntityManagerInterface $entityManager, private readonly RegistryRepository $registryRepository, private readonly VersionRepository $versionRepository, @@ -70,7 +72,7 @@ public function resolve(Package $package): void $this->updatePackage($package, $composerPackages); - $this->providerManager->dump($package, $composerPackages); + $this->messenger->dispatch(new DumpPackageProvider($package->getId())); } public function findPackageProvider(string $packageName): ?Registry @@ -177,14 +179,39 @@ private function updateVersion(Package $package, Version $version, CompletePacka $version->setIncludePaths($data->getIncludePaths()); $version->setSupport($data->getSupport()); $version->setFunding($data->getFunding()); - $version->setHomepage($data->getHomepage()); $version->setLicense($data->getLicense() ?: []); + $version->setType($this->sanitize($data->getType())); $version->setPackage($package); $version->setUpdatedAt(new \DateTime()); $version->setReleasedAt($data->getReleaseDate()); + $version->setAuthors([]); + if ($data->getAuthors()) { + $authors = []; + foreach ($data->getAuthors() as $authorData) { + $author = []; + + foreach (['email', 'name', 'homepage', 'role'] as $field) { + if (isset($authorData[$field])) { + $author[$field] = trim($authorData[$field]); + if ('' === $author[$field]) { + unset($author[$field]); + } + } + } + + // skip authors with no information + if (!isset($authorData['email']) && !isset($authorData['name'])) { + continue; + } + + $authors[] = $author; + } + $version->setAuthors($authors); + } + if ($data->getSourceType()) { $source['type'] = $data->getSourceType(); $source['url'] = $data->getSourceUrl(); @@ -210,7 +237,7 @@ private function updateVersion(Package $package, Version $version, CompletePacka if ($data->isDefaultBranch()) { $package->setDescription($description); - $package->setRepositoryType($this->sanitize($data->getType())); + $package->setType($this->sanitize($data->getType())); if ($data->isAbandoned() && !$package->isAbandoned()) { //$io->write('Marking package abandoned as per composer metadata from '.$version->getVersion()); $package->setAbandoned(true); diff --git a/src/Package/PackageProviderManager.php b/src/Package/PackageProviderManager.php index 16bd97b..b33c75a 100644 --- a/src/Package/PackageProviderManager.php +++ b/src/Package/PackageProviderManager.php @@ -4,7 +4,6 @@ use CodedMonkey\Conductor\Doctrine\Entity\Package; use Composer\MetadataMinifier\MetadataMinifier; -use Composer\Package\Dumper\ArrayDumper; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Filesystem\Filesystem; @@ -21,16 +20,21 @@ public function __construct( $this->storagePath = "$storagePath/provider"; } - public function dump(Package $package, array $composerPackages): void + public function dump(Package $package): void { $releasePackages = []; $devPackages = []; - foreach ($composerPackages as $composerPackage) { - if (!$composerPackage->isDev()) { - $releasePackages[] = $composerPackage; + $versions = $package->getVersions()->toArray(); + usort($versions, [Package::class, 'sortVersions']); + + foreach ($versions as $version) { + $versionData = $version->toComposerArray(); + + if (!$version->isDevelopment()) { + $releasePackages[] = $versionData; } else { - $devPackages[] = $composerPackage; + $devPackages[] = $versionData; } } @@ -52,7 +56,6 @@ public function path(string $packageName): string private function write(string $packageName, array $composerPackages, bool $development = false): void { - $path = $this->path(!$development ? $packageName : "{$packageName}~dev"); $data = $this->compile($packageName, $composerPackages); @@ -65,12 +68,10 @@ private function write(string $packageName, array $composerPackages, bool $devel private function compile(string $packageName, array $composerPackages): array { - $data = array_map([new ArrayDumper(), 'dump'], $composerPackages); - return [ 'minified' => 'composer/2.0', 'packages' => [ - $packageName => MetadataMinifier::minify($data), + $packageName => MetadataMinifier::minify($composerPackages), ], ]; }