diff --git a/Converter/NpmPackageUtil.php b/Converter/NpmPackageUtil.php index 312b1e0..0461f69 100644 --- a/Converter/NpmPackageUtil.php +++ b/Converter/NpmPackageUtil.php @@ -53,11 +53,11 @@ public static function revertName(string $name): string /** * Convert the npm licenses list. * - * @param array|string $licenses The npm package licenses list + * @param array|string|null $licenses The npm package licenses list * - * @return array|string + * @return array|string|null */ - public static function convertLicenses(array|string $licenses): array|string + public static function convertLicenses(array|string|null $licenses): array|string|null { if (!\is_array($licenses)) { return $licenses; @@ -82,11 +82,11 @@ public static function convertLicenses(array|string $licenses): array|string /** * Convert the author section. * - * @param string|null $value The current value + * @param string|array|null $value The current value * * @return array|null */ - public static function convertAuthor(?string $value): array|null + public static function convertAuthor(string|array|null $value): array|null { if (null !== $value) { $value = [$value]; diff --git a/Converter/SemverUtil.php b/Converter/SemverUtil.php index 2d35dc7..13c8da0 100644 --- a/Converter/SemverUtil.php +++ b/Converter/SemverUtil.php @@ -144,7 +144,7 @@ protected static function cleanVersion(string $version, array $matches): array $matches = []; preg_match('/^[a-z]+/', $end, $matches); - $type = isset($matches[0]) ? VersionParser::normalizeStability($matches[0]) : null; + $type = isset($matches[0]) ? VersionParser::normalizeStability($matches[0]) : ''; $end = substr($end, \strlen($type)); return [$type, $version, $end]; diff --git a/Installer/AssetInstaller.php b/Installer/AssetInstaller.php index c7413ea..b8b96ba 100644 --- a/Installer/AssetInstaller.php +++ b/Installer/AssetInstaller.php @@ -19,6 +19,8 @@ use Fxp\Composer\AssetPlugin\Config\Config; use Fxp\Composer\AssetPlugin\Type\AssetTypeInterface; use Fxp\Composer\AssetPlugin\Util\AssetPlugin; +use React\Promise\PromiseInterface; +use function React\Promise\resolve; /** * Installer for asset packages. @@ -42,7 +44,7 @@ class AssetInstaller extends LibraryInstaller * @param AssetTypeInterface $assetType * @param Filesystem|null $filesystem */ - public function __construct(Config $config, IOInterface $io, Composer $composer, AssetTypeInterface $assetType, Filesystem $filesystem = null) + public function __construct(Config $config, IOInterface $io, Composer $composer, AssetTypeInterface $assetType, ?Filesystem $filesystem = null) { parent::__construct($io, $composer, $assetType->getComposerType(), $filesystem); @@ -89,25 +91,35 @@ protected function getPackageBasePath(PackageInterface $package): string /** * {@inheritDoc} */ - protected function installCode(PackageInterface $package): void + protected function installCode(PackageInterface $package): PromiseInterface|null { $package = AssetPlugin::addMainFiles($this->config, $package); - parent::installCode($package); + $promise = parent::installCode($package); + if (!$promise instanceof PromiseInterface) { + $promise = resolve(); + } - $this->deleteIgnoredFiles($package); + return $promise->then(function () use ($package) { + $this->deleteIgnoredFiles($package); + }); } /** * {@inheritDoc} */ - protected function updateCode(PackageInterface $initial, PackageInterface $target): void + protected function updateCode(PackageInterface $initial, PackageInterface $target): PromiseInterface|null { $target = AssetPlugin::addMainFiles($this->config, $target); - parent::updateCode($initial, $target); + $promise = parent::updateCode($initial, $target); + if (!$promise instanceof PromiseInterface) { + $promise = resolve(); + } - $this->deleteIgnoredFiles($target); + return $promise->then(function () use ($target) { + $this->deleteIgnoredFiles($target); + }); } /** diff --git a/Package/AbstractLazyCompletePackage.php b/Package/AbstractLazyCompletePackage.php index 1284fc1..e61f80a 100644 --- a/Package/AbstractLazyCompletePackage.php +++ b/Package/AbstractLazyCompletePackage.php @@ -59,7 +59,7 @@ public function getIncludePaths(): array /** * {@inheritDoc} */ - public function getNotificationUrl(): string + public function getNotificationUrl(): ?string { $this->initialize(); @@ -129,7 +129,7 @@ public function getAuthors(): array /** * {@inheritDoc} */ - public function getDescription(): string + public function getDescription(): ?string { $this->initialize(); @@ -139,7 +139,7 @@ public function getDescription(): string /** * {@inheritDoc} */ - public function getHomepage(): string + public function getHomepage(): ?string { $this->initialize(); diff --git a/Package/LazyCompletePackage.php b/Package/LazyCompletePackage.php index 2022402..187c102 100644 --- a/Package/LazyCompletePackage.php +++ b/Package/LazyCompletePackage.php @@ -31,7 +31,7 @@ public function getTransportOptions(): array /** * {@inheritDoc} */ - public function getTargetDir(): string + public function getTargetDir(): ?string { $this->initialize(); @@ -61,7 +61,7 @@ public function getBinaries(): array /** * {@inheritDoc} */ - public function getInstallationSource(): string + public function getInstallationSource(): ?string { $this->initialize(); @@ -71,7 +71,7 @@ public function getInstallationSource(): string /** * {@inheritDoc} */ - public function getSourceType(): string + public function getSourceType(): ?string { $this->initialize(); @@ -81,7 +81,7 @@ public function getSourceType(): string /** * {@inheritDoc} */ - public function getSourceUrl(): string + public function getSourceUrl(): ?string { $this->initialize(); @@ -91,7 +91,7 @@ public function getSourceUrl(): string /** * {@inheritDoc} */ - public function getSourceReference(): string + public function getSourceReference(): ?string { $this->initialize(); @@ -101,7 +101,7 @@ public function getSourceReference(): string /** * {@inheritDoc} */ - public function getSourceMirrors(): array + public function getSourceMirrors(): ?array { $this->initialize(); @@ -121,7 +121,7 @@ public function getSourceUrls(): array /** * {@inheritDoc} */ - public function getDistType(): string + public function getDistType(): ?string { $this->initialize(); @@ -131,7 +131,7 @@ public function getDistType(): string /** * {@inheritDoc} */ - public function getDistUrl(): string + public function getDistUrl(): ?string { $this->initialize(); @@ -141,7 +141,7 @@ public function getDistUrl(): string /** * {@inheritDoc} */ - public function getDistReference(): string + public function getDistReference(): ?string { $this->initialize(); @@ -151,7 +151,7 @@ public function getDistReference(): string /** * {@inheritDoc} */ - public function getDistSha1Checksum(): string + public function getDistSha1Checksum(): ?string { $this->initialize(); @@ -161,7 +161,7 @@ public function getDistSha1Checksum(): string /** * {@inheritDoc} */ - public function getDistMirrors(): array + public function getDistMirrors(): ?array { $this->initialize(); @@ -181,7 +181,7 @@ public function getDistUrls(): array /** * {@inheritDoc} */ - public function getReleaseDate(): \DateTimeInterface + public function getReleaseDate(): ?\DateTimeInterface { $this->initialize(); diff --git a/Package/Loader/LazyAssetPackageLoader.php b/Package/Loader/LazyAssetPackageLoader.php index b343b79..d0c191d 100644 --- a/Package/Loader/LazyAssetPackageLoader.php +++ b/Package/Loader/LazyAssetPackageLoader.php @@ -158,7 +158,7 @@ public function setAssetRepositoryManager(AssetRepositoryManager $assetRepositor /** * {@inheritDoc} */ - public function load(LazyPackageInterface $package): LazyPackageInterface|false + public function load(LazyPackageInterface $package): CompletePackageInterface|false { if (isset($this->cache[$package->getUniqueName()])) { return $this->cache[$package->getUniqueName()]; diff --git a/README.md b/README.md index 8241266..f01a366 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ -NPM/Bower Dependency Manager for Composer -========================================= +NPM/Bower Dependency Manager for Composer v2 +============================================ -[![Latest Version](https://img.shields.io/packagist/v/fxp/composer-asset-plugin.svg)](https://packagist.org/packages/fxp/composer-asset-plugin) -[![Build Status](https://img.shields.io/travis/com/fxpio/composer-asset-plugin.svg)](https://travis-ci.com/fxpio/composer-asset-plugin) -[![Coverage Status](https://img.shields.io/coveralls/fxpio/composer-asset-plugin.svg)](https://coveralls.io/r/fxpio/composer-asset-plugin) -[![SymfonyInsight](https://img.shields.io/symfony/i/grade/0d67ca33-5a72-46b8-b109-cfbf95673fce.svg)](https://insight.symfony.com/projects/0d67ca33-5a72-46b8-b109-cfbf95673fce) -[![Packagist Downloads](https://img.shields.io/packagist/dt/fxp/composer-asset-plugin.svg)](https://packagist.org/packages/fxp/composer-asset-plugin/stats) +[![Latest Stable Version](https://poser.pugx.org/simialbi/composer-asset-plugin/v/stable?format=flat-square)](https://packagist.org/packages/simialbi/composer-asset-plugin) +[![Total Downloads](https://poser.pugx.org/simialbi/composer-asset-plugin/downloads?format=flat-square)](https://packagist.org/packages/simialbi/composer-asset-plugin) +[![License](https://poser.pugx.org/simialbi/composer-asset-plugin/license?format=flat-square)](https://packagist.org/packages/simialbi/composer-asset-plugin) +[![build](https://github.com/simialbi/composer-asset-plugin/actions/workflows/build.yml/badge.svg)](https://github.com/simialbi/composer-asset-plugin/actions/workflows/build.yml) The Composer Asset Plugin allows you to manage project assets (css, js, etc.) in your `composer.json` without installing NPM or Bower. @@ -105,7 +104,7 @@ The bulk of the documentation is located in `Resources/doc/index.md`: [Read the FAQs](Resources/doc/faqs.md) -[Read the Release Notes](https://github.com/fxpio/composer-asset-plugin/releases) +[Read the Release Notes](https://github.com/simialbi/composer-asset-plugin/releases) Installation ------------ diff --git a/Repository/AbstractAssetVcsRepository.php b/Repository/AbstractAssetVcsRepository.php index b0c2fed..4999100 100644 --- a/Repository/AbstractAssetVcsRepository.php +++ b/Repository/AbstractAssetVcsRepository.php @@ -173,6 +173,7 @@ protected function initLoader(): void /** * Initializes the root identifier. * + * @param VcsDriverInterface $driver * @return void */ protected function initRootIdentifier(VcsDriverInterface $driver): void @@ -221,12 +222,12 @@ protected function createPackageName(): string * @return array The package config */ #[ArrayShape(['name' => 'string', 'version' => 'string', 'type' => 'string'])] - protected function createMockOfPackageConfig(string $name, string $version) + protected function createMockOfPackageConfig(string $name, string $version): array { return [ 'name' => $name, 'version' => $version, - 'type' => $this->assetType->getComposerType(), + 'type' => $this->assetType->getComposerType() ]; } @@ -279,6 +280,7 @@ protected function preProcessAsset(array $data): array * @param string $branch The branch name * * @return PackageInterface + * @throws \ReflectionException */ protected function overrideBranchAliasConfig(PackageInterface $package, string $aliasNormalized, string $branch): PackageInterface { @@ -303,6 +305,7 @@ protected function overrideBranchAliasConfig(PackageInterface $package, string $ */ protected function addPackageAliases(PackageInterface $package, string $aliasNormalized): PackageInterface { + /** @var \Composer\Package\BasePackage $package */ $alias = new AliasPackage($package, $aliasNormalized, $this->rootPackageVersion); $this->addPackage($alias); diff --git a/Repository/AbstractAssetsRepository.php b/Repository/AbstractAssetsRepository.php index 7762311..1ca5d17 100644 --- a/Repository/AbstractAssetsRepository.php +++ b/Repository/AbstractAssetsRepository.php @@ -12,15 +12,18 @@ namespace Fxp\Composer\AssetPlugin\Repository; use Composer\Config; -use Composer\DependencyResolver\Pool; use Composer\Downloader\TransportException; use Composer\EventDispatcher\EventDispatcher; use Composer\IO\IOInterface; -use Composer\Json\JsonFile; +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; use Composer\Pcre\Preg; use Composer\Repository\ComposerRepository; use Composer\Repository\RepositoryManager; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; use Composer\Util\HttpDownloader; +use Composer\Util\ProcessExecutor; use Fxp\Composer\AssetPlugin\Assets; use Fxp\Composer\AssetPlugin\Type\AssetTypeInterface; use JetBrains\PhpStorm\ArrayShape; @@ -89,6 +92,16 @@ abstract class AbstractAssetsRepository extends ComposerRepository */ protected HttpDownloader $httpDownloader; + /** + * @var Config + */ + protected Config $config; + + /** + * @var ProcessExecutor + */ + protected ProcessExecutor $process; + /** * @var string */ @@ -107,8 +120,16 @@ abstract class AbstractAssetsRepository extends ComposerRepository * @param Config $config * @param HttpDownloader $httpDownloader * @param EventDispatcher|null $eventDispatcher + * @param ProcessExecutor|null $process */ - public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null) + public function __construct( + array $repoConfig, + IOInterface $io, + Config $config, + HttpDownloader $httpDownloader, + ?EventDispatcher $eventDispatcher = null, + ?ProcessExecutor $process = null + ) { $repoConfig = array_merge($repoConfig, [ 'url' => $this->getUrl(), @@ -118,7 +139,11 @@ public function __construct(array $repoConfig, IOInterface $io, Config $config, parent::__construct($repoConfig, $io, $config, $httpDownloader, $eventDispatcher); + $this->io = $io; + $this->repoConfig = $repoConfig; $this->httpDownloader = $httpDownloader; + $this->config = $config; + $this->process = $process; $this->url = $repoConfig['url']; $this->baseUrl = rtrim(Preg::replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/'); $this->assetType = Assets::createType($this->getType()); @@ -155,44 +180,119 @@ public function search(string $query, int $mode = 0, ?string $type = null): arra } /** + * {@inheritDoc} + * @throws \Exception + */ + #[ArrayShape([ + 'namesFound' => 'array', + 'packages' => 'array' + ])] + public function loadPackages(array $packageNameMap, array $acceptableStability, array $stabilityFlags, array $alreadyLoaded = []): array + { + $packages = []; + $namesFound = []; + + if ($this->hasProviders) { + foreach ($packageNameMap as $name => $constraint) { + $matches = []; +// if (is_null($constraint) || $name === 'bower-asset/jquery') { +// var_dump($name, $packageNameMap, $acceptableStability); +// debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); +// exit; +// } + $candidates = $this->whatProvides($name, $constraint, $acceptableStability, $stabilityFlags, $alreadyLoaded); + foreach ($candidates as $candidate) { + if ($candidate->getName() !== $name) { + var_dump($candidate->getName(), $name); + exit(); + } + + $namesFound[$name] = true; + + if (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) { + $matches[spl_object_hash($candidate)] = $candidate; + if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) { + $matches[spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf(); + } + } + } + + // add aliases of matched packages even if they did not match the constraint + foreach ($candidates as $candidate) { + if ($candidate instanceof AliasPackage) { + if (isset($matches[spl_object_hash($candidate->getAliasOf())])) { + $matches[spl_object_hash($candidate)] = $candidate; + } + } + } + $packages = array_merge($packages, $matches); + } + } + + return [ + 'namesFound' => $namesFound, + 'packages' => $packages + ]; + } + + /** + * Search package by name * @param string $name + * @param ConstraintInterface $constraint * @param array|null $acceptableStability * @param array|null $stabilityFlags * @param array $alreadyLoaded * - * @return array - * @throws \Exception + * @return BasePackage[] + * @throws \Composer\Repository\RepositorySecurityException */ protected function whatProvides( - string $name, - array $acceptableStability = null, - array $stabilityFlags = null, - array $alreadyLoaded = [] + string $name, + ConstraintInterface $constraint, + ?array $acceptableStability = null, + ?array $stabilityFlags = null, + array $alreadyLoaded = [] ): array { - if (null !== ($provides = $this->findWhatProvides($name))) { - return $provides; + if (!str_starts_with($name, "{$this->getType()}-asset/")) { + return []; } + $packages = null; try { $repoName = Util::convertAliasName($name); $packageName = Util::cleanPackageName($repoName); $packageUrl = $this->buildPackageUrl($packageName); - $cacheName = $packageName . '-' . sha1($packageName) . '-package.json'; - $data = $this->fetchFile($packageUrl, $cacheName); - $repo = $this->createVcsRepositoryConfig($data, Util::cleanPackageName($name)); - $repo['asset-repository-manager'] = $this->assetRepositoryManager; - $repo['vcs-package-filter'] = $this->packageFilter; - $repo['vcs-driver-options'] = Util::getArrayValue($this->repoConfig, 'vcs-driver-options', []); + $cacheKey = $packageName . '-' . strtr($name, '/', '$') . '-package.json'; - Util::addRepository($this->io, $this->repositoryManager, $this->repos, $name, $repo); + if ($contents = $this->cache->read($cacheKey)) { + $contents = json_decode($contents, true); - $this->providers[$name] = []; - } catch (\Exception $ex) { - $this->whatProvidesManageException($name, $ex); + if (isset($alreadyLoaded[$name])) { + $packages = $contents; + } + } + + if (!$packages) { + $data = $this->fetchFile($packageUrl, $cacheKey); + $repo = $this->createVcsRepositoryConfig($data, Util::cleanPackageName($name)); + $repo['asset-repository-manager'] = $this->assetRepositoryManager; + $repo['vcs-package-filter'] = $this->packageFilter; + $repo['vcs-driver-options'] = Util::getArrayValue($this->repoConfig, 'vcs-driver-options', []); + $repo = Util::addRepository($this->io, $this->repositoryManager, $this->repos, $name, $repo); + /** @var \Fxp\Composer\AssetPlugin\Repository\AssetVcsRepository $repo */ + + $packages = $repo->loadPackages([$repoName => $constraint], $acceptableStability, $stabilityFlags, $alreadyLoaded)['packages']; + } + } catch (TransportException $e) { + try { + $this->whatProvidesManageException($name, $e); + } catch (\Exception $e) { + } } - return $this->providers[$name]; + + return $packages; } /** @@ -252,6 +352,7 @@ protected function findWhatProvides(string $name): ?array * @param string $name The package name of the vcs repository * * @return bool + * @throws \Composer\Repository\InvalidRepositoryException */ protected function hasVcsRepository(string $name): bool { diff --git a/Repository/NpmRepository.php b/Repository/NpmRepository.php index 2fa8e0d..f5731b0 100644 --- a/Repository/NpmRepository.php +++ b/Repository/NpmRepository.php @@ -59,6 +59,7 @@ protected function getPackageUrl(): string { return $this->canonicalizeUrl($this->baseUrl . '/%package%'); } + /** * {@inheritDoc} */ @@ -80,6 +81,7 @@ protected function buildPackageUrl(string $packageName): string /** * {@inheritDoc} + * @throws InvalidCreateRepositoryException */ #[ArrayShape([ 'type' => 'string', diff --git a/Repository/Util.php b/Repository/Util.php index a86e8e1..5d3c7fc 100644 --- a/Repository/Util.php +++ b/Repository/Util.php @@ -79,7 +79,7 @@ public static function addRepositoryInstance( $rm->addRepository($repo); } - return $notAddedRepo; + return $notAddedRepo ?? $repos[$name]; } /** diff --git a/Repository/Vcs/AbstractGitHubDriver.php b/Repository/Vcs/AbstractGitHubDriver.php index 1e2a9bd..417fb19 100644 --- a/Repository/Vcs/AbstractGitHubDriver.php +++ b/Repository/Vcs/AbstractGitHubDriver.php @@ -13,7 +13,6 @@ use Composer\Cache; use Composer\Downloader\TransportException; -use Composer\Json\JsonFile; use Composer\Repository\Vcs\GitHubDriver as BaseGitHubDriver; use Composer\Util\Http\Response; use Fxp\Composer\AssetPlugin\Repository\Util as RepoUtil; @@ -33,7 +32,7 @@ abstract class AbstractGitHubDriver extends BaseGitHubDriver /** * @var null|false|string */ - protected string|null|false $redirectApi; + protected string|null|false $redirectApi = null; /** * {@inheritDoc} @@ -238,7 +237,9 @@ protected function getRepositoryApiUrl(?string $owner = null, ?string $repositor */ protected function getRemoteContents(string $url): Response { - return $this->httpDownloader->get($url, []); + $options = $this->repoConfig['options'] ?? []; + + return $this->httpDownloader->get($url, $options); } /** diff --git a/Repository/Vcs/GitDriver.php b/Repository/Vcs/GitDriver.php index 6c3d25d..fe1cbd9 100644 --- a/Repository/Vcs/GitDriver.php +++ b/Repository/Vcs/GitDriver.php @@ -49,7 +49,8 @@ public function initialize(): void } $cacheUrl = Filesystem::isLocalPath($this->url) - ? $this->initializeLocalPath() : $this->initializeRemotePath($skipSync); + ? $this->initializeLocalPath() + : $this->initializeRemotePath($skipSync); $this->getTags(); $this->getBranches(); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir') . '/' . preg_replace('{[^a-z0-9.]}i', '-', $cacheUrl)); diff --git a/Repository/Vcs/Util.php b/Repository/Vcs/Util.php index df167c3..2cfcadb 100644 --- a/Repository/Vcs/Util.php +++ b/Repository/Vcs/Util.php @@ -14,6 +14,7 @@ use Composer\Cache; use Composer\Json\JsonFile; use Composer\Repository\Vcs\VcsDriverInterface; +use Composer\Util\Http\Response; /** * Helper for VCS driver. @@ -109,7 +110,10 @@ public static function addComposerTime( $meth = $ref->getMethod($method); $meth->setAccessible(true); - $commit = JsonFile::parseJson($meth->invoke($driver, $resource), $resource); + /** @var Response $response */ + $response = $meth->invoke($driver, $resource); + + $commit = $response->decodeJson(); $keys = explode('.', $resourceKey); while (!empty($keys)) { diff --git a/Repository/VcsPackageFilter.php b/Repository/VcsPackageFilter.php index 89e4cc8..79efb10 100644 --- a/Repository/VcsPackageFilter.php +++ b/Repository/VcsPackageFilter.php @@ -215,7 +215,7 @@ protected function forceSkipVersion(string $normalizedVersion): bool protected function satisfyVersion(Link $require, string $normalizedVersion): bool { $constraintSame = $this->versionParser->parseConstraints($normalizedVersion); - $sameVersion = (bool)$require->getConstraint()->matches($constraintSame); + $sameVersion = $require->getConstraint()->matches($constraintSame); $consNormalizedVersion = FilterUtil::getVersionConstraint($normalizedVersion, $this->versionParser); $constraint = FilterUtil::getVersionConstraint($consNormalizedVersion->getPrettyString(), $this->versionParser); diff --git a/Util/Perforce.php b/Util/Perforce.php index 25d4a98..d5fc5c5 100644 --- a/Util/Perforce.php +++ b/Util/Perforce.php @@ -13,6 +13,7 @@ use Composer\IO\IOInterface; use Composer\Util\Perforce as BasePerforce; +use Composer\Util\Platform; use Composer\Util\ProcessExecutor; /** @@ -25,7 +26,7 @@ class Perforce extends BasePerforce /** * @var string */ - protected $filename; + protected string $filename; /** * {@inheritDoc} @@ -54,8 +55,6 @@ public function getComposerInformation(string $identifier): ?array */ public static function create($repoConfig, string|int $port, string $path, ProcessExecutor $process, IOInterface $io): static { - $isWindows = \defined('PHP_WINDOWS_VERSION_BUILD'); - - return new self($repoConfig, $port, $path, $process, $isWindows, $io); + return new self($repoConfig, $port, $path, $process, Platform::isWindows(), $io); } }