diff --git a/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php b/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php
index 7021bba762cb6..a81857b5c14b2 100644
--- a/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php
+++ b/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php
@@ -15,7 +15,9 @@
use Symfony\Component\AssetMapper\AssetMapperRepository;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -40,10 +42,41 @@ public function __construct(
protected function configure(): void
{
$this
+ ->addArgument('name', InputArgument::OPTIONAL, 'An asset name (or a path) to search for (e.g. "app")')
+ ->addOption('ext', null, InputOption::VALUE_REQUIRED, 'Filter assets by extension (e.g. "css")', null, ['js', 'css', 'png'])
->addOption('full', null, null, 'Whether to show the full paths')
+ ->addOption('vendor', null, InputOption::VALUE_NEGATABLE, 'Only show assets from vendor packages')
->setHelp(<<<'EOT'
-The %command.name% command outputs all of the assets in
-asset mapper for debugging purposes.
+The %command.name% command displays information about the Asset
+Mapper for debugging purposes.
+
+To list all configured paths (with local paths and their namespace prefixes) and
+all mapped assets (with their logical path and filesystem path), run:
+
+ php %command.full_name%
+
+You can filter the results by providing a name to search for in the asset name
+or path:
+
+ php %command.full_name% bootstrap.js
+ php %command.full_name% style/
+
+To filter the assets by extension, use the --ext option:
+
+ php %command.full_name% --ext=css
+
+To show only assets from vendor packages, use the --vendor option:
+
+ php %command.full_name% --vendor
+
+To exclude assets from vendor packages, use the --no-vendor option:
+
+ php %command.full_name% --no-vendor
+
+To see the full paths, use the --full option:
+
+ php %command.full_name% --full
+
EOT
);
}
@@ -52,43 +85,83 @@ protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
- $allAssets = $this->assetMapper->allAssets();
+ $name = $input->getArgument('name');
+ $extensionFilter = $input->getOption('ext');
+ $vendorFilter = $input->getOption('vendor');
+
+ if (!$extensionFilter) {
+ $io->section($name ? 'Matched Paths' : 'Asset Mapper Paths');
+ $pathRows = [];
+ foreach ($this->assetMapperRepository->allDirectories() as $path => $namespace) {
+ $path = $this->relativizePath($path);
+ if (!$input->getOption('full')) {
+ $path = $this->shortenPath($path);
+ }
+ if ($name && !str_contains($path, $name) && !str_contains($namespace, $name)) {
+ continue;
+ }
+ $pathRows[] = [$path, $namespace];
+ }
+ uasort($pathRows, static function (array $a, array $b): int {
+ return [(bool) $a[1], ...$a] <=> [(bool) $b[1], ...$b];
+ });
+ if ($pathRows) {
+ $io->table(['Path', 'Namespace prefix'], $pathRows);
+ } else {
+ $io->warning('No paths found.');
+ }
+ }
- $pathRows = [];
- foreach ($this->assetMapperRepository->allDirectories() as $path => $namespace) {
- $path = $this->relativizePath($path);
+ $io->section($name ? 'Matched Assets' : 'Mapped Assets');
+ $rows = $this->searchAssets($name, $extensionFilter, $vendorFilter);
+ if ($rows) {
if (!$input->getOption('full')) {
- $path = $this->shortenPath($path);
+ $rows = array_map(fn (array $row): array => [
+ $this->shortenPath($row[0]),
+ $this->shortenPath($row[1]),
+ ], $rows);
}
-
- $pathRows[] = [$path, $namespace];
+ uasort($rows, static function (array $a, array $b): int {
+ return [$a] <=> [$b];
+ });
+ $io->table(['Logical Path', 'Filesystem Path'], $rows);
+ if ($this->didShortenPaths) {
+ $io->note('To see the full paths, re-run with the --full option.');
+ }
+ } else {
+ $io->warning('No assets found.');
}
- $io->section('Asset Mapper Paths');
- $io->table(['Path', 'Namespace prefix'], $pathRows);
+ return 0;
+ }
+
+ /**
+ * @return list
+ */
+ private function searchAssets(?string $name, ?string $extension, ?bool $vendor): array
+ {
$rows = [];
- foreach ($allAssets as $asset) {
+ foreach ($this->assetMapper->allAssets() as $asset) {
+ if ($extension && $extension !== $asset->publicExtension) {
+ continue;
+ }
+ if (null !== $vendor && $vendor !== $asset->isVendor) {
+ continue;
+ }
+ if ($name && !str_contains($asset->logicalPath, $name) && !str_contains($asset->sourcePath, $name)) {
+ continue;
+ }
+
$logicalPath = $asset->logicalPath;
$sourcePath = $this->relativizePath($asset->sourcePath);
- if (!$input->getOption('full')) {
- $logicalPath = $this->shortenPath($logicalPath);
- $sourcePath = $this->shortenPath($sourcePath);
- }
-
$rows[] = [
$logicalPath,
$sourcePath,
];
}
- $io->section('Mapped Assets');
- $io->table(['Logical Path', 'Filesystem Path'], $rows);
-
- if ($this->didShortenPaths) {
- $io->note('To see the full paths, re-run with the --full option.');
- }
- return 0;
+ return $rows;
}
private function relativizePath(string $path): string
diff --git a/src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php b/src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php
index 5d2530004096c..03fe35450a085 100644
--- a/src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php
+++ b/src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php
@@ -31,4 +31,57 @@ public function testCommandDumpsInformation()
$this->assertStringContainsString('subdir/file6.js', $tester->getDisplay());
$this->assertStringContainsString('dir2'.\DIRECTORY_SEPARATOR.'subdir'.\DIRECTORY_SEPARATOR.'file6.js', $tester->getDisplay());
}
+
+ public function testCommandFiltersName()
+ {
+ $application = new Application(new AssetMapperTestAppKernel('test', true));
+ $command = $application->find('debug:asset-map');
+ $tester = new CommandTester($command);
+ $res = $tester->execute(['name' => 'stimulus']);
+
+ $this->assertSame(0, $res);
+ $this->assertStringContainsString('stimulus', $tester->getDisplay());
+ $this->assertStringNotContainsString('lodash', $tester->getDisplay());
+
+ $res = $tester->execute(['name' => 'lodash']);
+ $this->assertSame(0, $res);
+ $this->assertStringNotContainsString('stimulus', $tester->getDisplay());
+ $this->assertStringContainsString('lodash', $tester->getDisplay());
+ }
+
+ public function testCommandFiltersExtension()
+ {
+ $application = new Application(new AssetMapperTestAppKernel('test', true));
+ $command = $application->find('debug:asset-map');
+ $tester = new CommandTester($command);
+ $res = $tester->execute(['--ext' => 'css']);
+
+ $this->assertSame(0, $res);
+ $this->assertStringNotContainsString('.js', $tester->getDisplay());
+
+ $this->assertStringContainsString('file1.css', $tester->getDisplay());
+ $this->assertStringContainsString('file3.css', $tester->getDisplay());
+ }
+
+ public function testCommandFiltersVendor()
+ {
+ $application = new Application(new AssetMapperTestAppKernel('test', true));
+ $command = $application->find('debug:asset-map');
+
+ $tester = new CommandTester($command);
+ $res = $tester->execute(['--vendor' => true]);
+
+ $this->assertSame(0, $res);
+ $this->assertStringContainsString('vendor/lodash/', $tester->getDisplay());
+ $this->assertStringContainsString('@hotwired/stimulus', $tester->getDisplay());
+ $this->assertStringNotContainsString('dir2'.\DIRECTORY_SEPARATOR, $tester->getDisplay());
+
+ $tester = new CommandTester($command);
+ $res = $tester->execute(['--no-vendor' => true]);
+
+ $this->assertSame(0, $res);
+ $this->assertStringNotContainsString('vendor/lodash/', $tester->getDisplay());
+ $this->assertStringNotContainsString('@hotwired/stimulus', $tester->getDisplay());
+ $this->assertStringContainsString('dir2'.\DIRECTORY_SEPARATOR, $tester->getDisplay());
+ }
}