diff --git a/src/Symfony/Component/AssetMapper/CHANGELOG.md b/src/Symfony/Component/AssetMapper/CHANGELOG.md index 93d622101c0c8..d71897373f4b4 100644 --- a/src/Symfony/Component/AssetMapper/CHANGELOG.md +++ b/src/Symfony/Component/AssetMapper/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.4 +--- + + * Add "full" type for a package to download all files + 7.3 --- diff --git a/src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php b/src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php index 3a1efabc9cd7b..914ec5a297f2d 100644 --- a/src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php +++ b/src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php @@ -14,6 +14,7 @@ use Symfony\Component\AssetMapper\ImportMap\ImportMapEntries; use Symfony\Component\AssetMapper\ImportMap\ImportMapEntry; use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\AssetMapper\ImportMap\ImportMapType; use Symfony\Component\AssetMapper\ImportMap\ImportMapVersionChecker; use Symfony\Component\AssetMapper\ImportMap\PackageRequireOptions; use Symfony\Component\Console\Attribute\AsCommand; @@ -51,6 +52,7 @@ protected function configure(): void ->addArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The packages to add') ->addOption('entrypoint', null, InputOption::VALUE_NONE, 'Make the packages an entrypoint?') ->addOption('path', null, InputOption::VALUE_REQUIRED, 'The local path where the package lives relative to the project root') + ->addOption('type', null, InputOption::VALUE_REQUIRED, 'The package type, specified for specific download.') ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Simulate the installation of the packages') ->setHelp(<<<'EOT' The %command.name% command adds packages to importmap.php usually @@ -124,6 +126,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $parts['alias'] ?? null, $path, $input->getOption('entrypoint'), + $input->getOption('type') ? ImportMapType::tryfrom($input->getOption('type')) : null, ); } diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php index 00c265bc4635d..7a0ea8de95643 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php @@ -162,7 +162,7 @@ public function requirePackages(array $packagesToRequire, ImportMapEntries $impo $newEntry = ImportMapEntry::createLocal( $requireOptions->importName, - self::getImportMapTypeFromFilename($requireOptions->path), + $requireOptions->importMapType ?? self::getImportMapTypeFromFilename($requireOptions->path), $path, $requireOptions->entrypoint, ); diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapType.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapType.php index 99c8ff270c739..989c45c70f411 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapType.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapType.php @@ -15,4 +15,15 @@ enum ImportMapType: string { case JS = 'js'; case CSS = 'css'; + case FULL = 'full'; + + public function hasMainFile(): bool + { + if($this == self::FULL) + { + return false; + } + + return true; + } } diff --git a/src/Symfony/Component/AssetMapper/ImportMap/PackageRequireOptions.php b/src/Symfony/Component/AssetMapper/ImportMap/PackageRequireOptions.php index c1bb34a8f66cd..b09642d1b85f1 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/PackageRequireOptions.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/PackageRequireOptions.php @@ -29,6 +29,7 @@ public function __construct( ?string $importName = null, public readonly ?string $path = null, public readonly bool $entrypoint = false, + public readonly ?ImportMapType $importMapType = null, ) { $this->importName = $importName ?: $packageModuleSpecifier; } diff --git a/src/Symfony/Component/AssetMapper/ImportMap/RemotePackageDownloader.php b/src/Symfony/Component/AssetMapper/ImportMap/RemotePackageDownloader.php index 47b6a14598728..eaec80f254256 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/RemotePackageDownloader.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/RemotePackageDownloader.php @@ -76,7 +76,10 @@ public function downloadPackages(?callable $progressCallback = null): array throw new \LogicException(\sprintf('The package "%s" was not downloaded.', $package)); } - $this->remotePackageStorage->save($entry, $contents[$package]['content']); + if($contents[$package]['hasMainFile']) { + $this->remotePackageStorage->save($entry, $contents[$package]['content']); + } + foreach ($contents[$package]['extraFiles'] as $extraFilename => $extraFileContents) { $this->remotePackageStorage->saveExtraFile($entry, $extraFilename, $extraFileContents); } diff --git a/src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php b/src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php index b88f0e792d44f..19c1141736a87 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php @@ -27,6 +27,7 @@ final class JsDelivrEsmResolver implements PackageResolverInterface public const URL_PATTERN_DIST_CSS = 'https://cdn.jsdelivr.net/npm/%s@%s%s'; public const URL_PATTERN_DIST = self::URL_PATTERN_DIST_CSS.'/+esm'; public const URL_PATTERN_ENTRYPOINT = 'https://data.jsdelivr.com/v1/packages/npm/%s@%s/entrypoints'; + public const URL_PATTERN_FULL_DOWNLOAD = 'https://data.jsdelivr.com/v1/packages/npm/%s@%s'; public const IMPORT_REGEX = '#(?:import\s*(?:[\w$]+,)?(?:(?:\{[^}]*\}|[\w$]+|\*\s*as\s+[\w$]+)\s*\bfrom\s*)?|export\s*(?:\{[^}]*\}|\*)\s*from\s*|await\simport\()("/npm/((?:@[^/]+/)?[^@]+?)(?:@([^/]+))?((?:/[^/]+)*?)/\+esm")(?:\)*)#'; @@ -80,7 +81,7 @@ public function resolvePackages(array $packagesToRequire): array throw new RuntimeException(\sprintf('Unable to find the latest version for package "%s" - try specifying the version manually.', $packageName)); } - $pattern = $this->resolveUrlPattern($packageName, $filePath); + $pattern = $this->resolveUrlPattern($packageName, $filePath, $options->importMapType); $requiredPackages[$i][1] = $this->httpClient->request('GET', \sprintf($pattern, $packageName, $version, $filePath)); $requiredPackages[$i][4] = $version; @@ -108,7 +109,7 @@ public function resolvePackages(array $packagesToRequire): array } $contentType = $response->getHeaders()['content-type'][0] ?? ''; - $type = str_starts_with($contentType, 'text/css') ? ImportMapType::CSS : ImportMapType::JS; + $type = $options->importMapType ?? (str_starts_with($contentType, 'text/css') ? ImportMapType::CSS : ImportMapType::JS); $resolvedPackages[$options->packageModuleSpecifier] = new ResolvedImportMapPackage($options, $version, $type); $packagesToRequire = array_merge($packagesToRequire, $this->fetchPackageRequirementsFromImports($response->getContent())); @@ -201,6 +202,7 @@ public function downloadPackages(array $importMapEntries, ?callable $progressCal 'content' => $this->makeImportsBare($response->getContent(), $dependencies, $extraFiles, $entry->type, $entry->getPackagePathString()), 'dependencies' => $dependencies, 'extraFiles' => [], + 'hasMainFile' => $entry->type->hasMainFile(), ]; if (0 !== \count($extraFiles)) { @@ -317,6 +319,24 @@ private function makeImportsBare(string $content, array &$dependencies, array &$ return $content; } + if (ImportMapType::FULL === $type) { + $data = json_decode($content); + $getFiles = function ($section, string $path) use(&$getFiles, &$extraFiles) { + foreach($section as $item) { + if($item->type == 'file') { + $extraFiles[] = $path . $item->name; + } + if($item->type == 'directory') { + $getFiles($item->files, $path . $item->name . '/' ); + } + } + }; + + $getFiles($data->files, '/'); + + return $content; + } + preg_match_all(CssAssetUrlCompiler::ASSET_URL_PATTERN, $content, $matches); foreach ($matches[1] as $path) { if (str_starts_with($path, 'data:')) { @@ -345,6 +365,10 @@ private function resolveUrlPattern(string $packageName, string $path, ?ImportMap return self::URL_PATTERN_DIST_CSS; } + if (ImportMapType::FULL === $type) { + return self::URL_PATTERN_FULL_DOWNLOAD; + } + return self::URL_PATTERN_DIST; } } diff --git a/src/Symfony/Component/AssetMapper/ImportMap/Resolver/PackageResolverInterface.php b/src/Symfony/Component/AssetMapper/ImportMap/Resolver/PackageResolverInterface.php index 354fa9d151be7..afdef1f7237f3 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/Resolver/PackageResolverInterface.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/Resolver/PackageResolverInterface.php @@ -37,7 +37,7 @@ public function resolvePackages(array $packagesToRequire): array; * * @param array $importMapEntries * - * @return array}> + * @return array, hasMainFile: bool}> */ public function downloadPackages(array $importMapEntries, ?callable $progressCallback = null): array; } diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/RemotePackageDownloaderTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/RemotePackageDownloaderTest.php index 6326036fc2ffd..45fb4eaa1124e 100644 --- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/RemotePackageDownloaderTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/RemotePackageDownloaderTest.php @@ -61,10 +61,10 @@ public function testDownloadPackagesDownloadsEverythingWithNoInstalled() $progressCallback ) ->willReturn([ - 'foo' => ['content' => 'foo content', 'dependencies' => [], 'extraFiles' => ['/path/to/extra-file.woff' => 'extra file contents']], - 'bar.js/file' => ['content' => 'bar content', 'dependencies' => [], 'extraFiles' => []], - 'baz' => ['content' => 'baz content', 'dependencies' => ['foo'], 'extraFiles' => []], - 'different_specifier' => ['content' => 'different content', 'dependencies' => [], 'extraFiles' => []], + 'foo' => ['content' => 'foo content', 'dependencies' => [], 'extraFiles' => ['/path/to/extra-file.woff' => 'extra file contents'], 'hasMainFile' => true], + 'bar.js/file' => ['content' => 'bar content', 'dependencies' => [], 'extraFiles' => [], 'hasMainFile' => true], + 'baz' => ['content' => 'baz content', 'dependencies' => ['foo'], 'extraFiles' => [], 'hasMainFile' => true], + 'different_specifier' => ['content' => 'different content', 'dependencies' => [], 'extraFiles' => [], 'hasMainFile' => true], ]); $downloader = new RemotePackageDownloader( @@ -131,9 +131,9 @@ public function testPackagesWithCorrectInstalledVersionSkipped() $packageResolver->expects($this->once()) ->method('downloadPackages') ->willReturn([ - 'bar.js/file' => ['content' => 'new bar content', 'dependencies' => [], 'extraFiles' => []], - 'baz' => ['content' => 'new baz content', 'dependencies' => [], 'extraFiles' => []], - 'has-missing-extra' => ['content' => 'new content', 'dependencies' => [], 'extraFiles' => ['/path/to/extra-file.woff' => 'extra file contents']], + 'bar.js/file' => ['content' => 'new bar content', 'dependencies' => [], 'extraFiles' => [], 'hasMainFile' => true], + 'baz' => ['content' => 'new baz content', 'dependencies' => [], 'extraFiles' => [], 'hasMainFile' => true], + 'has-missing-extra' => ['content' => 'new content', 'dependencies' => [], 'extraFiles' => ['/path/to/extra-file.woff' => 'extra file contents'], 'hasMainFile' => true], ]); $downloader = new RemotePackageDownloader( diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/Resolver/JsDelivrEsmResolverTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/Resolver/JsDelivrEsmResolverTest.php index ffb153fe54366..2da84991629be 100644 --- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/Resolver/JsDelivrEsmResolverTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/Resolver/JsDelivrEsmResolverTest.php @@ -305,7 +305,7 @@ public static function provideDownloadPackagesTests() ], ], [ - 'lodash' => ['content' => 'lodash contents', 'dependencies' => [], 'extraFiles' => []], + 'lodash' => ['content' => 'lodash contents', 'dependencies' => [], 'extraFiles' => [], 'hasMainFile' => true], ], ]; @@ -318,7 +318,7 @@ public static function provideDownloadPackagesTests() ], ], [ - 'lodash' => ['content' => 'lodash contents', 'dependencies' => [], 'extraFiles' => []], + 'lodash' => ['content' => 'lodash contents', 'dependencies' => [], 'extraFiles' => [], 'hasMainFile' => true], ], ]; @@ -331,7 +331,7 @@ public static function provideDownloadPackagesTests() ], ], [ - 'lodash' => ['content' => 'chart.js contents', 'dependencies' => [], 'extraFiles' => []], + 'lodash' => ['content' => 'chart.js contents', 'dependencies' => [], 'extraFiles' => [], 'hasMainFile' => true], ], ]; @@ -344,7 +344,7 @@ public static function provideDownloadPackagesTests() ], ], [ - 'lodash' => ['content' => 'bootstrap.css contents', 'dependencies' => [], 'extraFiles' => []], + 'lodash' => ['content' => 'bootstrap.css contents', 'dependencies' => [], 'extraFiles' => [], 'hasMainFile' => true], ], ]; @@ -369,9 +369,9 @@ public static function provideDownloadPackagesTests() ], ], [ - 'lodash' => ['content' => 'lodash contents', 'dependencies' => [], 'extraFiles' => []], - 'chart.js/auto' => ['content' => 'chart.js contents', 'dependencies' => [], 'extraFiles' => []], - 'bootstrap/dist/bootstrap.css' => ['content' => 'bootstrap.css contents', 'dependencies' => [], 'extraFiles' => []], + 'lodash' => ['content' => 'lodash contents', 'dependencies' => [], 'extraFiles' => [], 'hasMainFile' => true], + 'chart.js/auto' => ['content' => 'chart.js contents', 'dependencies' => [], 'extraFiles' => [], 'hasMainFile' => true], + 'bootstrap/dist/bootstrap.css' => ['content' => 'bootstrap.css contents', 'dependencies' => [], 'extraFiles' => [], 'hasMainFile' => true], ], ]; @@ -390,6 +390,7 @@ public static function provideDownloadPackagesTests() 'content' => 'import{Color as t}from"@kurkle/color";function e(){}const i=(()=', 'dependencies' => ['@kurkle/color'], 'extraFiles' => [], + 'hasMainFile' => true, ], ], ]; @@ -409,6 +410,7 @@ public static function provideDownloadPackagesTests() 'content' => 'import e from"locutus/php/strings/sprintf";console.log()', 'dependencies' => ['locutus/php/strings/sprintf'], 'extraFiles' => [], + 'hasMainFile' => true, ], ], ]; @@ -429,6 +431,7 @@ public static function provideDownloadPackagesTests() 'content' => 'as Ticks,ta as TimeScale,ia as TimeSeriesScale,oo as Title,wo as Tooltip,Ci as _adapters,us as _detectPlatform,Ye as animator,Si as controllers,tn as default,St as defaults,Pn as elements,qi as layouts,ko as plugins,na as registerables,Ps as registry,sa as scales};', 'dependencies' => [], 'extraFiles' => [], + 'hasMainFile' => true, ], ], ]; @@ -453,6 +456,7 @@ public static function provideDownloadPackagesTests() EOF, 'dependencies' => [], 'extraFiles' => [], + 'hasMainFile' => true, ], ], ]; @@ -471,6 +475,7 @@ public static function provideDownloadPackagesTests() 'content' => 'print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}', 'dependencies' => [], 'extraFiles' => [], + 'hasMainFile' => true, ], ], ];