diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php index b73d70fcfcd11..6a376c58e072d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php @@ -19,6 +19,7 @@ use Symfony\Component\AssetMapper\Command\AssetMapperCompileCommand; use Symfony\Component\AssetMapper\Command\DebugAssetMapperCommand; use Symfony\Component\AssetMapper\Command\ImportMapExportCommand; +use Symfony\Component\AssetMapper\Command\ImportMapInstallCommand; use Symfony\Component\AssetMapper\Command\ImportMapRemoveCommand; use Symfony\Component\AssetMapper\Command\ImportMapRequireCommand; use Symfony\Component\AssetMapper\Command\ImportMapUpdateCommand; @@ -202,5 +203,9 @@ ->set('asset_mapper.importmap.command.export', ImportMapExportCommand::class) ->args([service('asset_mapper.importmap.manager')]) ->tag('console.command') + + ->set('asset_mapper.importmap.command.install', ImportMapInstallCommand::class) + ->args([service('asset_mapper.importmap.manager')]) + ->tag('console.command') ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index a4525d9724a4d..d5518c297fab5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -37,7 +37,7 @@ "doctrine/persistence": "^1.3|^2|^3", "seld/jsonlint": "^1.10", "symfony/asset": "^5.4|^6.0|^7.0", - "symfony/asset-mapper": "^6.3|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", "symfony/browser-kit": "^5.4|^6.0|^7.0", "symfony/console": "^5.4.9|^6.0.9|^7.0", "symfony/clock": "^6.2|^7.0", @@ -79,6 +79,7 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/asset": "<5.4", + "symfony/asset-mapper": "<6.4", "symfony/clock": "<6.3", "symfony/console": "<5.4", "symfony/dotenv": "<5.4", diff --git a/src/Symfony/Component/AssetMapper/CHANGELOG.md b/src/Symfony/Component/AssetMapper/CHANGELOG.md index 140d728dbfa51..77f32884bdc02 100644 --- a/src/Symfony/Component/AssetMapper/CHANGELOG.md +++ b/src/Symfony/Component/AssetMapper/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Mark the component as non experimental + * Add a `importmap:install` command to download all missing downloaded packages 6.3 --- diff --git a/src/Symfony/Component/AssetMapper/Command/ImportMapInstallCommand.php b/src/Symfony/Component/AssetMapper/Command/ImportMapInstallCommand.php new file mode 100644 index 0000000000000..6924deddc55ca --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Command/ImportMapInstallCommand.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Command; + +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * Downloads all assets that should be downloaded. + * + * @author Jonathan Scheiber + */ +#[AsCommand(name: 'importmap:install', description: 'Downloads all assets that should be downloaded.')] +final class ImportMapInstallCommand extends Command +{ + public function __construct( + private readonly ImportMapManager $importMapManager, + ) { + parent::__construct(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $downloadedPackages = $this->importMapManager->downloadMissingPackages(); + $io->success(sprintf('Downloaded %d assets.', \count($downloadedPackages))); + + return Command::SUCCESS; + } +} diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php index 699dcdae2cade..3b8559f715315 100644 --- a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php +++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php @@ -13,6 +13,7 @@ use Symfony\Component\AssetMapper\AssetDependency; use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\Exception\RuntimeException; use Symfony\Component\AssetMapper\ImportMap\Resolver\PackageResolverInterface; use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolverInterface; use Symfony\Component\VarExporter\VarExporter; @@ -108,6 +109,36 @@ public function update(): array return $this->updateImportMapConfig(true, [], []); } + /** + * Downloads all missing downloaded packages. + * + * @return ImportMapEntry[] The downloaded packages + */ + public function downloadMissingPackages(): array + { + $entries = $this->loadImportMapEntries(); + $packagesToDownload = []; + + foreach ($entries as $entry) { + if (!$entry->isDownloaded || $this->assetMapper->getAsset($entry->path)) { + continue; + } + + $parts = self::parsePackageName($entry->url); + + $packagesToDownload[] = new PackageRequireOptions( + $parts['package'], + $parts['version'] ?? throw new RuntimeException(sprintf('Cannot get a version for the "%s" package.', $parts['package'])), + true, + $entry->preload, + $parts['alias'] ?? $parts['package'], + isset($parts['registry']) && $parts['registry'] ? $parts['registry'] : null, + ); + } + + return $this->require($packagesToDownload); + } + /** * @internal */ diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php index c947d334909e4..86f3a96d4e1dc 100644 --- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php @@ -373,6 +373,43 @@ public function testUpdate() $this->assertSame('contents of cowsay.js', $actualContents); } + public function testDownloadMissingPackages() + { + $rootDir = __DIR__.'/../fixtures/download'; + $manager = $this->createImportMapManager(['assets' => ''], $rootDir); + + $this->packageResolver->expects($this->once()) + ->method('resolvePackages') + ->willReturn([ + self::resolvedPackage('@hotwired/stimulus', 'https://cdn.jsdelivr.net/npm/stimulus@3.2.1/+esm', true, content: 'contents of stimulus.js'), + ]) + ; + + $downloadedPackages = $manager->downloadMissingPackages(); + $actualImportMap = require $rootDir.'/importmap.php'; + $expectedImportMap = [ + '@hotwired/stimulus' => [ + 'downloaded_to' => 'vendor/@hotwired/stimulus.js', + 'url' => 'https://cdn.jsdelivr.net/npm/stimulus@3.2.1/+esm', + ], + 'lodash' => [ + 'downloaded_to' => 'vendor/lodash.js', + 'url' => 'https://ga.jspm.io/npm:lodash@4.17.21/lodash.js', + ], + ]; + $this->assertEquals($expectedImportMap, $actualImportMap); + + $expectedDownloadedFiles = [ + 'assets/vendor/@hotwired/stimulus.js' => 'contents of stimulus.js', + ]; + foreach ($expectedDownloadedFiles as $file => $expectedContents) { + $this->assertFileExists($rootDir.'/'.$file); + $actualContents = file_get_contents($rootDir.'/'.$file); + $this->assertSame($expectedContents, $actualContents); + unlink($rootDir.'/'.$file); + } + } + /** * @dataProvider getPackageNameTests */ diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/download/assets/vendor/lodash.js b/src/Symfony/Component/AssetMapper/Tests/fixtures/download/assets/vendor/lodash.js new file mode 100644 index 0000000000000..ac1d7f73afb58 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/download/assets/vendor/lodash.js @@ -0,0 +1 @@ +console.log('fake downloaded lodash.js'); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/download/importmap.php b/src/Symfony/Component/AssetMapper/Tests/fixtures/download/importmap.php new file mode 100644 index 0000000000000..30bb5a9469f59 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/download/importmap.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '@hotwired/stimulus' => [ + 'downloaded_to' => 'vendor/@hotwired/stimulus.js', + 'url' => 'https://cdn.jsdelivr.net/npm/stimulus@3.2.1/+esm', + ], + 'lodash' => [ + 'downloaded_to' => 'vendor/lodash.js', + 'url' => 'https://ga.jspm.io/npm:lodash@4.17.21/lodash.js', + ], +];