8000 [AssetMapper] Add a "package specifier" to importmap in case import n… · symfony/symfony@c80a1ab · GitHub
[go: up one dir, main page]

Skip to content

Commit c80a1ab

Browse files
weaverryanfabpot
authored andcommitted
[AssetMapper] Add a "package specifier" to importmap in case import name != package+path
1 parent 7074da9 commit c80a1ab

34 files changed

+773
-452
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -1363,8 +1363,8 @@ private function registerAssetMapperConfiguration(array $config, ContainerBuilde
13631363
->setArgument(1, $config['missing_import_mode']);
13641364

13651365
$container
1366-
->getDefinition('asset_mapper.importmap.remote_package_downloader')
1367-
->replaceArgument(2, $config['vendor_dir'])
1366+
->getDefinition('asset_mapper.importmap.remote_package_storage')
1367+
->replaceArgument(0, $config['vendor_dir'])
13681368
;
13691369
$container
13701370
->getDefinition('asset_mapper.mapped_asset_factory')

src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer;
3636
use Symfony\Component\AssetMapper\ImportMap\ImportMapUpdateChecker;
3737
use Symfony\Component\AssetMapper\ImportMap\RemotePackageDownloader;
38+
use Symfony\Component\AssetMapper\ImportMap\RemotePackageStorage;
3839
use Symfony\Component\AssetMapper\ImportMap\Resolver\JsDelivrEsmResolver;
3940
use Symfony\Component\AssetMapper\MapperAwareAssetPackage;
4041
use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolver;
@@ -145,6 +146,7 @@
145146
->set('asset_mapper.importmap.config_reader', ImportMapConfigReader::class)
146147
->args([
147148
abstract_arg('importmap.php path'),
149+
service('asset_mapper.importmap.remote_package_storage'),
148150
])
149151

150152
->set('asset_mapper.importmap.manager', ImportMapManager::class)
@@ -157,11 +159,16 @@
157159
])
158160
->alias(ImportMapManager::class, 'asset_mapper.importmap.manager')
159161

162+
->set('asset_mapper.importmap.remote_package_storage', RemotePackageStorage::class)
163+
->args([
164+
abstract_arg('vendor directory'),
165+
])
166+
160167
->set('asset_mapper.importmap.remote_package_downloader', RemotePackageDownloader::class)
161168
->args([
169+
service('asset_mapper.importmap.remote_package_storage'),
162170
service('asset_mapper.importmap.config_reader'),
163171
service('asset_mapper.importmap.resolver'),
164-
abstract_arg('vendor directory'),
165172
])
166173

167174
->set('asset_mapper.importmap.resolver', JsDelivrEsmResolver::class)

src/Symfony/Component/AssetMapper/Command/ImportMapOutdatedCommand.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
7373
return Command::SUCCESS;
7474
}
7575

76-
$displayData = array_map(fn ($importName, $packageUpdateInfo) => [
76+
$displayData = array_map(fn (string $importName, PackageUpdateInfo $packageUpdateInfo) => [
7777
'name' => $importName,
7878
'current' => $packageUpdateInfo->currentVersion,
7979
'latest' => $packageUpdateInfo->latestVersion,

src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php

+6-15
63
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,21 @@ protected function configure(): void
5454
5555
<info>php %command.full_name% "chart.js/auto"</info>
5656
57-
Or download one package/file, but alias its name in your import map:
57+
Or require one package/file, but alias its name in your import map:
5858
5959
<info>php %command.full_name% "vue/dist/vue.esm-bundler.js=vue"</info>
6060
61-
The <info>download</info> option will download the package locally and point the
62-
importmap to it. Use this if you want to avoid using a CDN or if you want to
-
ensure that the package is available even if the CDN is down.
64-
6561
Sometimes, a package may require other packages and multiple new items may be added
6662
to the import map.
6763
6864
You can also require multiple packages at once:
6965
7066
<info>php %command.full_name% "lodash@^4.15" "@hotwired/stimulus"</info>
7167
68+
To add an importmap entry pointing to a local file, use the <info>path</info> option:
69+
70+
<info>php %command.full_name% "any_module_name" --path=./assets/some_file.js</info>
71+
7272
EOT
7373
);
7474
}
@@ -87,15 +87,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8787
}
8888

8989
$path = $input->getOption('path');
90-
if (!is_file($path)) {
91-
$path = $this->projectDir.'/'.$path;
92-
93-
if (!is_file($path)) {
94-
$io->error(sprintf('The path "%s" does not exist.', $input->getOption('path')));
95-
96-
return Command::FAILURE;
97-
}
98-
}
9990
}
10091

10192
$packages = [];
@@ -110,7 +101,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
110101
$packages[] = new PackageRequireOptions(
111102
$parts['package'],
112103
$parts['version'] ?? null,
113-
$parts['a 179B lias'] ?? $parts['package'],
104+
$parts['alias'] ?? null,
114105
$path,
115106
$input->getOption('entrypoint'),
116107
);

src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php

+5-6
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ final class JavaScriptImportPathCompiler implements AssetCompilerInterface
2828
{
2929
use AssetCompilerPathResolverTrait;
3030

31-
// https://regex101.com/r/5Q38tj/1
32-
private const IMPORT_PATTERN = '/(?:import\s+(?:(?:\*\s+as\s+\w+|[\w\s{},*]+)\s+from\s+)?|\bimport\()\s*[\'"`](\.\/[^\'"`]+|(\.\.\/)*[^\'"`]+)[\'"`]\s*[;\)]?/m';
31+
// https://regex101.com/r/fquriB/1
32+
private const IMPORT_PATTERN = '/(?:import\s*(?:(?:\*\s*as\s+\w+|[\w\s{},*]+)\s*from\s*)?|\bimport\()\s*[\'"`](\.\/[^\'"`]+|(\.\.\/)*[^\'"`]+)[\'"`]\s*[;\)]?/m';
3333

3434
public function __construct(
3535
private readonly ImportMapManager $importMapManager,
@@ -145,12 +145,11 @@ private function findAssetForBareImport(string $importedModule, AssetMapperInter
145145
return null;
146146
}
147147

148-
// remote entries have no MappedAsset
149-
if ($importMapEntry->isRemotePackage()) {
150-
return null;
148+
if ($asset = $assetMapper->getAsset($importMapEntry->path)) {
149+
return $asset;
151150
}
152151

153-
return $assetMapper->getAsset($importMapEntry->path);
152+
return $assetMapper->getAssetFromSourcePath($importMapEntry->path);
154153
}
155154

156155
private function findAssetForRelativeImport(string $importedModule, MappedAsset $asset, AssetMapperInterface $assetMapper): ?MappedAsset
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\AssetMapper\Exception;
13+
14+
class LogicException extends \LogicException implements ExceptionInterface
15+
{
16+
}

src/Symfony/Component/AssetMapper/ImportMap/ImportMapAuditor.php

+10-9
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ public function audit(): array
3535
{
3636
$entries = $this->configReader->getEntries();
3737

38-
if (!$entries) {
39-
return [];
40-
}
41-
4238
/** @var array<string, array<string, ImportMapPackageAudit>> $installed */
4339
$packageAudits = [];
4440

@@ -51,14 +47,19 @@ public function audit(): array
5147
}
5248
$version = $entry->version;
5349

54-
$installed[$entry->importName] ??= [];
55-
$installed[$entry->importName][] = $version;
50+
$packageName = $entry->getPackageName();
51+
$installed[$packageName] ??= [];
52+
$installed[$packageName][] = $version;
5653

57-
$packageVersion = $entry->importName.($version ? '@'.$version : '');
58-
$packageAudits[$packageVersion] ??= new ImportMapPackageAudit($entry->importName, $version);
54+
$packageVersion = $packageName.'@'.$version;
55+
$packageAudits[$packageVersion] ??= new ImportMapPackageAudit($packageName, $version);
5956
$affectsQuery[] = $packageVersion;
6057
}
6158

59+
if (!$affectsQuery) {
60+
return [];
61+
}
62+
6263
// @see https://docs.github.com/en/rest/security-advisories/global-advisories?apiVersion=2022-11-28#list-global-security-advisories
6364
$response = $this->httpClient->request('GET', self::AUDIT_URL, [
6465
'query' => ['affects' => implode(',', $affectsQuery)],
@@ -81,7 +82,7 @@ public function audit(): array
8182
if (!$version || !$this->versionMatches($version, $vulnerability['vulnerable_version_range'] ?? '>= *')) {
8283
continue;
8384
}
84-
$packageAudits[$package.($version ? '@'.$version : '')] = $packageAudits[$package.($version ? '@'.$version : '')]->withVulnerability(
85+
$packageAudits[$package.'@'.$version] = $packageAudits[$package.'@'.$version]->withVulnerability(
8586
new ImportMapPackageAuditVulnerability(
8687
$advisory['ghsa_id'],
8788
$advisory['cve_id'],

src/Symfony/Component/AssetMapper/ImportMap/ImportMapConfigReader.php

+35-41
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ class ImportMapConfigReader
2323
{
2424
private ImportMapEntries $rootImportMapEntries;
2525

26-
public function __construct(private readonly string $importMapConfigPath)
27-
{
26+
public function __construct(
27+
private readonly string $importMapConfigPath,
28+
private readonly RemotePackageStorage $remotePackageStorage,
29+
) {
2830
}
2931

3032
public function getEntries(): ImportMapEntries
@@ -38,7 +40,7 @@ public function getEntries(): ImportMapEntries
3840

3941
$entries = new ImportMapEntries();
4042
foreach ($importMapConfig ?? [] as $importName => $data) {
41-
$validKeys = ['path', 'version', 'type', 'entrypoint', 'url'];
43+
$validKeys = ['path', 'version', 'type', 'entrypoint', 'url', 'package_specifier'];
4244
if ($invalidKeys = array_diff(array_keys($data), $validKeys)) {
4345
throw new \InvalidArgumentException(sprintf('The following keys are not valid for the importmap entry "%s": "%s". Valid keys are: "%s".', $importName, implode('", "', $invalidKeys), implode('", "', $validKeys)));
4446
}
@@ -49,36 +51,33 @@ public function getEntries(): ImportMapEntries
4951
}
5052

5153
$type = isset($data['type']) ? ImportMapType::tryFrom($data['type']) : ImportMapType::JS;
52-
$isEntry = $data['entrypoint'] ?? false;
54+
$isEntrypoint = $data['entrypoint'] ?? false;
55+
56+
if (isset($data['path'])) {
57+
if (isset($data['version'])) {
58+
throw new RuntimeException(sprintf('The importmap entry "%s" cannot have both a "path" and "version" option.', $importName));
59+
}
60+
if (isset($data['package_specifier'])) {
61+
throw new RuntimeException(sprintf('The importmap entry "%s" cannot have both a "path" and "package_specifier" option.', $importName));
62+
}
63+
64+
$entries->add(ImportMapEntry::createLocal($importName, $type, $data['path'], $isEntrypoint));
5365

54-
if ($isEntry && ImportMapType::JS !== $type) {
55-
throw new RuntimeException(sprintf('The "entrypoint" option can only be used with the "js" type. Found "%s" in importmap.php for key "%s".', $importName, $type->value));
66+
continue;
5667
}
5768

58-
$path = $data['path'] ?? null;
5969
$version = $data['version'] ?? null;
6070
if (null === $version && ($data['url'] ?? null)) {
6171
// BC layer for 6.3->6.4
6272
$version = $this->extractVersionFromLegacyUrl($data['url']);
6373
}
64-
if (null === $version && null === $path) {
74+
75+
if (null === $version) {
6576
throw new RuntimeException(sprintf('The importmap entry "%s" must have either a "path" or "version" option.', $importName));
6677
}
67-
if (null !== $version && null !== $path) {
68-
throw new RuntimeException(sprintf('The importmap entry "%s" cannot have both a "path" and "version" option.', $importName));
69-
}
7078

71-
[$packageName, $filePath] = self::splitPackageNameAndFilePath($importName);
72-
73-
$entries->add(new ImportMapEntry(
74-
$importName,
75-
path: $path,
76-
version: $version,
77-
type: $type,
78-
isEntrypoint: $isEntry,
79-
packageName: $packageName,
80-
filePath: $filePath,
81-
));
79+
$packageModuleSpecifier = $data['package_specifier'] ?? $importName;
80+
$entries->add($this->createRemoteEntry($importName, $type, $version, $packageModuleSpecifier, $isEntrypoint));
8281
}
8382

8483
return $this->rootImportMapEntries = $entries;
@@ -91,19 +90,21 @@ public function writeEntries(ImportMapEntries $entries): void
9190
$importMapConfig = [];
9291
foreach ($entries as $entry) {
9392
$config = [];
94-
if ($entry->path) {
95-
$path = $entry->path;
96-
$config['path'] = $path;
97-
}
98-
if ($entry->version) {
93+
if ($entry->isRemotePackage()) {
9994
$config['version'] = $entry->version;
95+
if ($entry->packageModuleSpecifier !== $entry->importName) {
96+
$config['package_specifier'] = $entry->packageModuleSpecifier;
97+
}
98+
} else {
99+
$config['path'] = $entry->path;
100100
}
101101
if (ImportMapType::JS !== $entry->type) {
102102
$config['type'] = $entry->type->value;
103103
}
104104
if ($entry->isEntrypoint) {
105105
$config['entrypoint'] = true;
106106
}
107+
107108
$importMapConfig[$entry->importName] = $config;
108109
}
109110

@@ -129,6 +130,13 @@ public function writeEntries(ImportMapEntries $entries): void
129130
EOF);
130131
}
131132

133+
public function createRemoteEntry(string $importName, ImportMapType $type, string $version, string $packageModuleSpecifier, bool $isEntrypoint): ImportMapEntry
134+
{
135+
$path = $this->remotePackageStorage->getDownloadPath($packageModuleSpecifier, $type);
136+
137+
return ImportMapEntry::createRemote($importName, $type, $path, $version, $packageModuleSpecifier, $isEntrypoint);
138+
}
139+
132140
public function getRootDirectory(): string
133141
{
134142
return \dirname($this->importMapConfigPath);
@@ -148,18 +156,4 @@ private function extractVersionFromLegacyUrl(string $url): ?string
148156

149157
return substr($url, $lastAt + 1, $nextSlash - $lastAt - 1);
150158
}
151-
152-
public static function splitPackageNameAndFilePath(string $packageName): array
153-
{
154-
$filePath = '';
155-
$i = strpos($packageName, '/');
156-
157-
if ($i && (!str_starts_with($packageName, '@') || $i = strpos($packageName, '/', $i + 1))) {
158-
// @vendor/package/filepath or package/filepath
159-
$filePath = substr($packageName, $i);
160-
$packageName = substr($packageName, 0, $i);
161-
}
162-
163-
return [$packageName, $filePath];
164-
}
165159
}

0 commit comments

Comments
 (0)
0