FestinHegre/vendor/symfony/asset-mapper/ImportMap/ImportMapManager.php
2024-09-26 17:26:04 +02:00

218 lines
7.0 KiB
PHP

<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\AssetMapper\ImportMap;
use Symfony\Component\AssetMapper\AssetMapperInterface;
use Symfony\Component\AssetMapper\ImportMap\Resolver\PackageResolverInterface;
use Symfony\Component\AssetMapper\MappedAsset;
/**
* @author Kévin Dunglas <kevin@dunglas.dev>
* @author Ryan Weaver <ryan@symfonycasts.com>
*
* @final
*/
class ImportMapManager
{
public function __construct(
private readonly AssetMapperInterface $assetMapper,
private readonly ImportMapConfigReader $importMapConfigReader,
private readonly RemotePackageDownloader $packageDownloader,
private readonly PackageResolverInterface $resolver,
) {
}
/**
* Adds or updates packages.
*
* @param PackageRequireOptions[] $packages
*
* @return ImportMapEntry[]
*/
public function require(array $packages): array
{
return $this->updateImportMapConfig(false, $packages, [], []);
}
/**
* Removes packages.
*
* @param string[] $packages
*/
public function remove(array $packages): void
{
$this->updateImportMapConfig(false, [], $packages, []);
}
/**
* Updates either all existing packages or the specified ones to the latest version.
*
* @return ImportMapEntry[]
*/
public function update(array $packages = []): array
{
return $this->updateImportMapConfig(true, [], [], $packages);
}
/**
* @internal
*/
public static function parsePackageName(string $packageName): ?array
{
// https://regex101.com/r/z1nj7P/1
$regex = '/((?P<package>@?[^=@\n]+))(?:@(?P<version>[^=\s\n]+))?(?:=(?P<alias>[^\s\n]+))?/';
if (!preg_match($regex, $packageName, $matches)) {
return null;
}
if (isset($matches['version']) && '' === $matches['version']) {
unset($matches['version']);
}
return $matches;
}
/**
* @param PackageRequireOptions[] $packagesToRequire
* @param string[] $packagesToRemove
*
* @return ImportMapEntry[]
*/
private function updateImportMapConfig(bool $update, array $packagesToRequire, array $packagesToRemove, array $packagesToUpdate): array
{
$currentEntries = $this->importMapConfigReader->getEntries();
foreach ($packagesToRemove as $packageName) {
if (!$currentEntries->has($packageName)) {
throw new \InvalidArgumentException(sprintf('Package "%s" listed for removal was not found in "importmap.php".', $packageName));
}
$this->cleanupPackageFiles($currentEntries->get($packageName));
$currentEntries->remove($packageName);
}
if ($update) {
foreach ($currentEntries as $entry) {
$importName = $entry->importName;
if (!$entry->isRemotePackage() || ($packagesToUpdate && !\in_array($importName, $packagesToUpdate, true))) {
continue;
}
$packagesToRequire[] = new PackageRequireOptions(
$entry->packageModuleSpecifier,
null,
$importName,
);
// remove it: then it will be re-added
$this->cleanupPackageFiles($entry);
$currentEntries->remove($importName);
}
}
$newEntries = $this->requirePackages($packagesToRequire, $currentEntries);
$this->importMapConfigReader->writeEntries($currentEntries);
$this->packageDownloader->downloadPackages();
return $newEntries;
}
/**
* Gets information about (and optionally downloads) the packages & updates the entries.
*
* Returns an array of the entries that were added.
*
* @param PackageRequireOptions[] $packagesToRequire
*/
private function requirePackages(array $packagesToRequire, ImportMapEntries $importMapEntries): array
{
if (!$packagesToRequire) {
return [];
}
$addedEntries = [];
// handle local packages
foreach ($packagesToRequire as $key => $requireOptions) {
if (null === $requireOptions->path) {
continue;
}
$path = $requireOptions->path;
if (!$asset = $this->findAsset($path)) {
throw new \LogicException(sprintf('The path "%s" of the package "%s" cannot be found: either pass the logical name of the asset or a relative path starting with "./".', $requireOptions->path, $requireOptions->importName));
}
// convert to a relative path (or fallback to the logical path)
$path = $asset->logicalPath;
if (null !== $relativePath = $this->importMapConfigReader->convertFilesystemPathToPath($asset->sourcePath)) {
$path = $relativePath;
}
$newEntry = ImportMapEntry::createLocal(
$requireOptions->importName,
self::getImportMapTypeFromFilename($requireOptions->path),
$path,
$requireOptions->entrypoint,
);
$importMapEntries->add($newEntry);
$addedEntries[] = $newEntry;
unset($packagesToRequire[$key]);
}
if (!$packagesToRequire) {
return $addedEntries;
}
$resolvedPackages = $this->resolver->resolvePackages($packagesToRequire);
foreach ($resolvedPackages as $resolvedPackage) {
$newEntry = $this->importMapConfigReader->createRemoteEntry(
$resolvedPackage->requireOptions->importName,
$resolvedPackage->type,
$resolvedPackage->version,
$resolvedPackage->requireOptions->packageModuleSpecifier,
$resolvedPackage->requireOptions->entrypoint,
);
$importMapEntries->add($newEntry);
$addedEntries[] = $newEntry;
}
return $addedEntries;
}
private function cleanupPackageFiles(ImportMapEntry $entry): void
{
$asset = $this->findAsset($entry->path);
if ($asset && is_file($asset->sourcePath)) {
@unlink($asset->sourcePath);
}
}
private static function getImportMapTypeFromFilename(string $path): ImportMapType
{
return str_ends_with($path, '.css') ? ImportMapType::CSS : ImportMapType::JS;
}
/**
* Finds the MappedAsset allowing for a "logical path", relative or absolute filesystem path.
*/
private function findAsset(string $path): ?MappedAsset
{
if ($asset = $this->assetMapper->getAsset($path)) {
return $asset;
}
return $this->assetMapper->getAssetFromSourcePath($this->importMapConfigReader->convertPathToFilesystemPath($path));
}
}