8000 [AssetMapper] Add `--dry-run` option on `importmap:require` command · symfony/symfony@337c465 · GitHub
[go: up one dir, main page]

Skip to content

Commit 337c465

Browse files
committed
[AssetMapper] Add --dry-run option on importmap:require command
1 parent 6a029ec commit 337c465

File tree

8 files changed

+329
-11
lines changed

8 files changed

+329
-11
lines changed

UPGRADE-7.3.md

+5
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,8 @@ VarDumper
191191

192192
* Deprecate `ResourceCaster::castCurl()`, `ResourceCaster::castGd()` and `ResourceCaster::castOpensslX509()`
193193
* Mark all casters as `@internal`
194+
195+
AssetMapper
196+
-----------
197+
198+
* `ImportMapRequireCommand` now takes `projectDir` as a required third constructor argument

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

+1
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@
232232
->args([
233233
service('asset_mapper.importmap.manager'),
234234
service('asset_mapper.importmap.version_checker'),
235+
param('kernel.project_dir'),
235236
])
236237
->tag('console.command')
237238

src/Symfony/Component/AssetMapper/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ CHANGELOG
55
---
66

77
* Add support for pre-compressing assets with Brotli, Zstandard, Zopfli, and gzip
8+
* Add option `--dry-run` to `importmap:require` command
9+
* `ImportMapRequireCommand` now takes `projectDir` as a required third constructor argument
810

911
7.2
1012
---

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

+43-9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\AssetMapper\Command;
1313

14+
use Symfony\Component\AssetMapper\ImportMap\ImportMapEntries;
1415
use Symfony\Component\AssetMapper\ImportMap\ImportMapEntry;
1516
use Symfony\Component\AssetMapper\ImportMap\ImportMapManager;
1617
use Symfony\Component\AssetMapper\ImportMap\ImportMapVersionChecker;
@@ -22,6 +23,7 @@
2223
use Symfony\Component\Console\Input\InputOption;
2324
use Symfony\Component\Console\Output\OutputInterface;
2425
use Symfony\Component\Console\Style\SymfonyStyle;
26+
use Symfony\Component\Filesystem\Path;
2527

2628
/**
2729
* @author Kévin Dunglas <kevin@dunglas.dev>
@@ -34,16 +36,22 @@ final class ImportMapRequireCommand extends Command
3436
public function __construct(
3537
private readonly ImportMapManager $importMapManager,
3638
private readonly ImportMapVersionChecker $importMapVersionChecker,
39+
private readonly ?string $projectDir = null,
3740
) {
41+
if (null === $projectDir) {
42+
trigger_deprecation('symfony/asset-mapper', '7.3', 'The "%s()" method will have a new `string $projectDir` argument in version 8.0, not defining it is deprecated.', __METHOD__);
43+
}
44+
3845
parent::__construct();
3946
}
4047

4148
protected function configure(): void
4249
{
4350
$this
4451
->addArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The packages to add')
45-
->addOption('entrypoint', null, InputOption::VALUE_NONE, 'Make the package(s) an entrypoint?')
52+
->addOption('entrypoint', null, InputOption::VALUE_NONE, 'Make the packages an entrypoint?')
4653
->addOption('path', null, InputOption::VALUE_REQUIRED, 'The local path where the package lives relative to the project root')
54+
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Simulate the installation of the packages')
4755
->setHelp(<<<'EOT'
4856
The <info>%command.name%</info> command adds packages to <comment>importmap.php</comment> usually
4957
by finding a CDN URL for the given package and version.
@@ -72,6 +80,11 @@ protected function configure(): void
7280
7381
<info>php %command.full_name% "any_module_name" --path=./assets/some_file.js</info>
7482
83+
To simulate the installation, use the <info>--dry-run</info> option:
84+
85+
<info>php %command.full_name% "any_module_name" --dry-run -v</info>
86+
87+
When this option is enabled, this command does not perform any write operations to the filesystem.
7588
EOT
7689
);
7790
}
@@ -92,6 +105,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
92105
$path = $input->getOption('path');
93106
}
94107

108+
if ($input->getOption('dry-run')) {
109+
$io->writeln(['', '<comment>[DRY-RUN]</comment> No changes will apply to the importmap configuration.', '']);
110+
}
111+
95112
$packages = [];
96113
foreach ($packageList as $packageName) {
97114
$parts = ImportMapManager::parsePackageName($packageName);
@@ -110,28 +127,45 @@ protected function execute(InputInterface $input, OutputInterface $output): int
110127
);
111128
}
112129

113-
$newPackages = $this->importMapManager->require($packages);
130+
if ($input->getOption('dry-run')) {
131+
$newPackages = $this->importMapManager->requirePackages($packages, new ImportMapEntries());
132+
} else {
133+
$newPackages = $this->importMapManager->require($packages);
134+
}
114135

115136
$this->renderVersionProblems($this->importMapVersionChecker, $output);
116137

117-
if (1 === \count($newPackages)) {
118-
$newPackage = $newPackages[0];
119-
$message = \sprintf('Package "%s" added to importmap.php', $newPackage->importName);
138+
$newPackageNames = array_map(fn (ImportMapEntry $package): string => $package->importName, $newPackages);
120139

121-
$message .= '.';
140+
if (1 === \count($newPackages)) {
141+
$messages = [\sprintf('Package "%s" added to importmap.php.', $newPackageNames[0])];
122142
} else {
123-
$names = array_map(fn (ImportMapEntry $package) => $package->importName, $newPackages);
124-
$message = \sprintf('%d new items (%s) added to the importmap.php!', \count($newPackages), implode(', ', $names));
143+
$messages = [\sprintf('%d new items (%s) added to the importmap.php!', \count($newPackages), implode(', ', $newPackageNames))];
125144
}
126145

127-
$messages = [$message];
146+
if ($io->isVerbose()) {
147+
$io->table(
148+
['Package', 'Version', 'Path'],
149+
array_map(fn (ImportMapEntry $package): array => [
150+
$package->importName,
151+
$package->version ?? '-',
152+
// BC layer for AssetMapper < 7.3
153+
// When `projectDir` is not null, we use the absolute path of the package
154+
null !== $this->projectDir ? Path::makeRelative($package->path, $this->projectDir) : $package->path,
155+
], $newPackages),
156+
);
157+
}
128158

129159
if (1 === \count($newPackages)) {
130160
$messages[] = \sprintf('Use the new package normally by importing "%s".', $newPackages[0]->importName);
131161
}
132162

133163
$io->success($messages);
134164

165+
if ($input->getOption('dry-run')) {
166+
$io->writeln(['<comment>[DRY-RUN]</comment> No changes applied to the importmap configuration.', '']);
167+
}
168+
135169
return Command::SUCCESS;
136170
}
137171
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,15 @@ private function updateImportMapConfig(bool $update, array $packagesToRequire, a
128128
}
129129

130130
/**
131+
* @internal
132+
*
131133
* Gets information about (and optionally downloads) the packages & updates the entries.
132134
*
133135
* Returns an array of the entries that were added.
134136
*
135137
* @param PackageRequireOptions[] $packagesToRequire
136138
*/
137-
private function requirePackages(array $packagesToRequire, ImportMapEntries $importMapEntries): array
139+
public function requirePackages(array $packagesToRequire, ImportMapEntries $importMapEntries): array
138140
{
139141
if (!$packagesToRequire) {
140142
return [];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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\Tests\Command;
13+
14+
use PHPUnit\Framework\Attributes\DataProvider;
15+
use Symfony\Bundle\FrameworkBundle\Console\Application;
16+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
17+
use Symfony\Component\AssetMapper\Command\ImportMapRequireCommand;
18+
use Symfony\Component\AssetMapper\ImportMap\ImportMapEntry;
19+
use Symfony\Component\AssetMapper\ImportMap\ImportMapManager;
20+
use Symfony\Component\AssetMapper\ImportMap\ImportMapType;
21+
use Symfony\Component\AssetMapper\ImportMap\ImportMapVersionChecker;
22+
use Symfony\Component\AssetMapper\Tests\Fixtures\ImportMapTestAppKernel;
23+
use Symfony\Component\Console\Output\OutputInterface;
24+
use Symfony\Component\Console\Tester\CommandTester;
25+
use Symfony\Component\Filesystem\Filesystem;
26+
use Symfony\Component\Finder\Finder;
27+
28+
class ImportMapRequireCommandTest extends KernelTestCase
29+
{
30+
protected static function getKernelClass(): string
31+
{
32+
return ImportMapTestAppKernel::class;
33+
}
34+
35+
/**
36+
* @dataProvider getRequirePackageTests
37+
*/
38+
public function testDryRunOptionToShowInformationBeforeApplyInstallation(int $verbosity, array $packageEntries, array $packagesToInstall, string $expected, ?string $path = null)
39+
{
40+
$importMapManager = $this->createMock(ImportMapManager::class);
41+
$importMapManager
42+
->method('requirePackages')
43+
->willReturn($packageEntries)
44+
;
45+
46+
$command = new ImportMapRequireCommand(
47+
$importMapManager,
48+
$this->createMock(ImportMapVersionChecker::class),
49+
'/path/to/project/dir',
50+
);
51+
52+
$args = [
53+
'packages' => $packagesToInstall,
54+
'--dry-run' => true,
55+
];
56+
if ($path) {
57+
$args['--path'] = $path;
58+
}
59+
60+
$commandTester = new CommandTester($command);
61+
$commandTester->execute($args, ['verbosity' => $verbosity]);
62+
63+
$commandTester->assertCommandIsSuccessful();
64+
65+
$output = $commandTester->getDisplay();
66+
$this->assertEquals($this->trimBeginEndOfEachLine($expected), $this->trimBeginEndOfEachLine($output));
67+
}
68+
69+
public static function getRequirePackageTests(): iterable
70+
{
71+
yield 'require package with dry run and normal verbosity options' => [
72+
OutputInterface::VERBOSITY_NORMAL,
73+
[self::createRemoteEntry('bootstrap', '4.2.3', 'assets/vendor/bootstrap/bootstrap.js')],
74+
['bootstrap'], <<<EOF
75+
[DRY-RUN] No changes will apply to the importmap configuration.
76+
77+
[OK] Package "bootstrap" added to importmap.php.
78+
79+
Use the new package normally by importing "bootstrap".
80+
81+
[DRY-RUN] No changes applied to the importmap configuration.
82+
EOF,
83+
];
84+
85+
yield 'remote package requested with a version with dry run and verbosity verbose options' => [
86+
OutputInterface::VERBOSITY_VERBOSE,
87+
[self::createRemoteEntry('bootstrap', '5.3.3', 'assets/vendor/bootstrap/bootstrap.js')],
88+
['bootstrap'], <<<EOF
89+
[DRY-RUN] No changes will apply to the importmap configuration.
90+
91+
----------- --------- --------------------------------------
92+
Package Version Path
93+
----------- --------- --------------------------------------
94+
bootstrap 5.3.3 assets/vendor/bootstrap/bootstrap.js
95+
----------- --------- --------------------------------------
96+
97+
[OK] Package "bootstrap" added to importmap.php.
98+
99+
Use the new package normally by importing "bootstrap".
100+
101+
[DRY-RUN] No changes applied to the importmap configuration.
102+
EOF,
103+
];
104+
105+
yield 'local package require a path, with dry run and verbosity verbose options' => [
106+
OutputInterface::VERBOSITY_VERBOSE,
107+
[ImportMapEntry::createLocal('alice.js', ImportMapType::JS, 'assets/js/alice.js', false)],
108+
['alice.js'], <<<EOF
109+
[DRY-RUN] No changes will apply to the importmap configuration.
110+
111+
---------- --------- --------------------
112+
Package Version Path
113+
---------- --------- --------------------
114+
alice.js - assets/js/alice.js
115+
---------- --------- --------------------
116+
117+
[OK] Package "alice.js" added to importmap.php.
118+
119+
Use the new package normally by importing "alice.js".
120+
121+
[DRY-RUN] No changes applied to the importmap configuration.
122+
EOF,
123+
'./assets/alice.js',
124+
];
125+
126+
yield 'multi remote packages requested with dry run and verbosity normal options' => [
127+
OutputInterface::VERBOSITY_NORMAL, [
128+
self::createRemoteEntry('bootstrap', '5.3.3', 'assets/vendor/bootstrap/bootstrap.index.js'),
129+
self::createRemoteEntry('lodash', '4.17.20', 'assets/vendor/lodash/lodash.index.js'),
130+
],
131+
['bootstrap lodash@4.17.21'], <<<EOF
132+
[DRY-RUN] No changes will apply to the importmap configuration.
133+
134+
[OK] 2 new items (bootstrap, lodash) added to the importmap.php!
135+
136+
[DRY-RUN] No changes applied to the importmap configuration.
137+
EOF,
138+
];
139+
140+
yield 'multi remote packages requested with dry run and verbosity verbose options' => [
141+
OutputInterface::VERBOSITY_VERBOSE, [
142+
self::createRemoteEntry('bootstrap', '5.3.3', 'assets/vendor/bootstrap/bootstrap.js'),
143+
self::createRemoteEntry('lodash', '4.17.20', 'assets/vendor/lodash/lodash.index.js'),
144+
],
145+
['bootstrap lodash@4.17.21'], <<<EOF
146+
[DRY-RUN] No changes will apply to the importmap configuration.
147+
148+
----------- --------- --------------------------------------
149+
Package Version Path
150+
----------- --------- --------------------------------------
151+
bootstrap 5.3.3 assets/vendor/bootstrap/bootstrap.js
152+
lodash 4.17.20 assets/vendor/lodash/lodash.index.js
153+
----------- --------- --------------------------------------
154+
155+
[OK] 2 new items (bootstrap, lodash) added to the importmap.php!
156+
157+
[DRY-RUN] No changes applied to the importmap configuration.
158+
EOF,
159+
];
160+
}
161+
162+
public function testNothingChangedOnFilesystemAfterUsingDryRunOption()
163+
{
164+
$kernel = self::bootKernel();
165+
$projectDir = $kernel->getProjectDir();
166+
167+
$fs = new Filesystem();
168+
$fs->mkdir($projectDir.'/public');
169+
170+
$fs->dumpFile($projectDir.'/public/assets/manifest.json', '{}');
171+
$fs->dumpFile($projectDir.'/public/assets/importmap.json', '{}');
172+
173+
$importMapManager = $this->createMock(ImportMapManager::class);
174+
$importMapManager
175+
->expects($this->once())
176+
->method('requirePackages')
177+
->willReturn([self::createRemoteEntry('bootstrap', '5.3.3', 'assets/vendor/bootstrap/bootstrap.index.js')]);
178+
179+
self::getContainer()->set(ImportMapManager::class, $importMapManager);
180+
181+
$application = new Application(self::$kernel);
182+
$command = $application->find('importmap:require');
183+
184+
$importMapContentBefore = $fs->readFile($projectDir.'/importmap.php');
185+
$installedVendorBefore = $fs->readFile($projectDir.'/assets/vendor/installed.php');
186+
187+
$tester = new CommandTester($command);
188+
$tester->execute(['packages' => ['bootstrap'], '--dry-run' => true]);
189+
190+
$tester->assertCommandIsSuccessful();
191+
192+
$this->assertSame($importMapContentBefore, $fs->readFile($projectDir.'/importmap.php'));
193+
$this->assertSame($installedVendorBefore, $fs->readFile($projectDir.'/assets/vendor/installed.php'));
194+
195+
$this->assertSame('{}', $fs->readFile($projectDir.'/public/assets/manifest.json'));
196+
$this->assertSame('{}', $fs->readFile($projectDir.'/public/assets/importmap.json'));
197+
198+
$finder = new Finder();
199+
$finder->in($projectDir.'/public/assets')->files()->depth(0);
200+
201+
$this->assertCount(2, $finder); // manifest.json + importmap.json
202+
203+
$fs->remove($projectDir.'/public');
204+
$fs->remove($projectDir.'/var');
205+
206+
static::$kernel->shutdown();
207+
}
208+
209+
private static function createRemoteEntry(string $importName, string $version, ?string $path = null): ImportMapEntry
210+
{
211+
return ImportMapEntry::createRemote($importName, ImportMapType::JS, path: $path, version: $version, packageModuleSpecifier: $importName, isEntrypoint: false);
212+
}
213+
214+
private function trimBeginEndOfEachLine(string $lines): string
215+
{
216+
return trim(implode("\n", array_map('trim', explode("\n", $lines))));
217+
}
218+
}

src/Symfony/Component/AssetMapper/Tests/Fixtures/AssetMapperTestAppKernel.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public function registerContainerConfiguration(LoaderInterface $loader): void
4444
'assets' => null,
4545
'asset_mapper' => [
4646
'paths' => ['dir1', 'dir2', 'non_ascii', 'assets'],
47-
'public_prefix' => 'assets'
47+
'public_prefix' => 'assets',
4848
],
4949
'test' => true,
5050
]);

0 commit comments

Comments
 (0)
0