8000 [AssetMapper] Adding debug:assetmap command + normalize paths by weaverryan · Pull Request #50219 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[AssetMapper] Adding debug:assetmap command + normalize paths #50219

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Symfony\Component\AssetMapper\AssetMapperInterface;
use Symfony\Component\AssetMapper\AssetMapperRepository;
use Symfony\Component\AssetMapper\Command\AssetMapperCompileCommand;
use Symfony\Component\AssetMapper\Command\DebugAssetMapperCommand;
use Symfony\Component\AssetMapper\Command\ImportMapExportCommand;
use Symfony\Component\AssetMapper\Command\ImportMapRemoveCommand;
use Symfony\Component\AssetMapper\Command\ImportMapRequireCommand;
Expand Down Expand Up @@ -70,6 +71,14 @@
])
->tag('console.command')

->set('asset_mapper.command.debug', DebugAssetMapperCommand::class)
->args([
service('asset_mapper'),
service('asset_mapper.repository'),
param('kernel.project_dir'),
])
->tag('console.command')

->set('asset_mapper_compiler', AssetMapperCompiler::class)
->args([
tagged_iterator('asset_mapper.compiler'),
Expand Down
34 changes: 29 additions & 5 deletions src/Symfony/Component/AssetMapper/AssetMapperRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public function find(string $logicalPath): ?string

$file = rtrim($path, '/').'/'.$localLogicalPath;
if (file_exists($file)) {
return $file;
return realpath($file);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using realpath everywhere now in this file so that all paths use the "real" path. That's just "nice" because you'll get back full paths - instead of potentially getting back file paths like /var/something/else/../other-path/app.js - and is important especially for findLogicalPath() where we pass in a $filesystemPath and then do some matching by the filesystem path.

}
}

Expand All @@ -64,17 +64,24 @@ public function find(string $logicalPath): ?string

public function findLogicalPath(string $filesystemPath): ?string
{
if (!is_file($filesystemPath)) {
return null;
}

$filesystemPath = realpath($filesystemPath);

foreach ($this->getDirectories() as $path => $namespace) {
if (!str_starts_with($filesystemPath, $path)) {
continue;
}

$logicalPath = substr($filesystemPath, \strlen($path));

if ('' !== $namespace) {
$logicalPath = $namespace.'/'.$logicalPath;
$logicalPath = $namespace.'/'.ltrim($logicalPath, '/\\');
}

return ltrim($logicalPath, '/');
return $this->normalizeLogicalPath($logicalPath);
}

return null;
Expand All @@ -100,13 +107,22 @@ public function all(): array
/** @var RecursiveDirectoryIterator $innerIterator */
$innerIterator = $iterator->getInnerIterator();
$logicalPath = ($namespace ? rtrim($namespace, '/').'/' : '').$innerIterator->getSubPathName();
$logicalPath = $this->normalizeLogicalPath($logicalPath);
$paths[$logicalPath] = $file->getPathname();
}
}

return $paths;
}

/**
* @internal
*/
public function allDirectories(): array
{
return $this->getDirectories();
}

private function getDirectories(): array
{
$filesystem = new Filesystem();
Expand All @@ -120,13 +136,13 @@ private function getDirectories(): array
if (!file_exists($path)) {
throw new \InvalidArgumentException(sprintf('The asset mapper directory "%s" does not exist.', $path));
}
$this->absolutePaths[$path] = $namespace;
$this->absolutePaths[realpath($path)] = $namespace;

continue;
}

if (file_exists($this->projectRootDir.'/'.$path)) {
$this->absolutePaths[$this->projectRootDir.'/'.$path] = $namespace;
$this->absolutePaths[realpath($this->projectRootDir.'/'.$path)] = $namespace;

continue;
}
Expand All @@ -136,4 +152,12 @@ private function getDirectories(): array

return $this->absolutePaths;
}

/**
* Normalize slashes to / for logical paths.
*/
private function normalizeLogicalPath(string $logicalPath): string
{
return ltrim(str_replace('\\', '/', $logicalPath), '/\\');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
*
* @author Ryan Weaver <ryan@symfonycasts.com>
*/
#[AsCommand(name: 'assetmap:compile', description: 'Compiles all mapped assets and writes them to the final public output directory.')]
#[AsCommand(name: 'asset-map:compile', description: 'Compiles all mapped assets and writes them to the final public output directory.')]
final class AssetMapperCompileCommand extends Command
{
public function __construct(
Expand Down Expand Up @@ -105,6 +105,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
));
}

return self::SUCCESS;
return 0;
}
}
114 changes: 114 additions & 0 deletions src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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\AssetMapperInterface;
use Symfony\Component\AssetMapper\AssetMapperRepository;
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;

/**
* Outputs all the assets in the asset mapper.
*
* @experimental
*
* @author Ryan Weaver <ryan@symfonycasts.com>
*/
#[AsCommand(name: 'debug:asset-map', description: 'Outputs all mapped assets.')]
final class DebugAssetMapperCommand extends Command
{
private bool $didShortenPaths = false;

public function __construct(
private readonly AssetMapperInterface $assetMapper,
private readonly AssetMapperRepository $assetMapperRepository,
private readonly string $projectDir,
) {
parent::__construct();
}

protected function configure(): void
{
$this
->addOption('full', null, null, 'Whether to show the full paths')
->setHelp(<<<'EOT'
The <info>%command.name%</info> command outputs all of the assets in
asset mapper for debugging purposes.
EOT
);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

$allAssets = $this->assetMapper->allAssets();

$pathRows = [];
foreach ($this->assetMapperRepository->allDirectories() as $path => $namespace) {
$path = $this->relativizePath($path);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could make AssetMapperRepository::getRepositories() able to return the relative paths with some bool argument or even a new method. No big deal though, fine as is

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true - we already have the projectRootDir there. I don't feel too strongly either way :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if only the debug command needs that, I would keep it in the bug command for now

if (!$input->getOption('full')) {
$path = $this->shortenPath($path);
}

$pathRows[] = [$path, $namespace];
}
$io->section('Asset Mapper Paths');
$io->table(['Path', 'Namespace prefix'], $pathRows);

$rows = [];
foreach ($allAssets as $asset) {
$logicalPath = $asset->logicalPath;
$sourcePath = $this->relativizePath($asset->getSourcePath());

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;
}

private function relativizePath(string $path): string
{
return str_replace($this->projectDir.'/', '', $path);
}

private function shortenPath($path): string
{
$limit = 50;

if (\strlen($path) <= $limit) {
return $path;
}

$this->didShortenPaths = true;
$limit = floor(($limit - 3) / 2);

return substr($path, 0, $limit).'...'.substr($path, -$limit);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ public function testFindWithAbsolutePaths()
__DIR__.'/fixtures/dir2' => '',
], __DIR__);

$this->assertSame(__DIR__.'/fixtures/dir1/file1.css', $repository->find('file1.css'));
$this->assertSame(__DIR__.'/fixtures/dir2/file4.js', $repository->find('file4.js'));
$this->assertSame(__DIR__.'/fixtures/dir2/subdir/file5.js', $repository->find('subdir/file5.js'));
$this->assertSame(realpath(__DIR__.'/fixtures/dir1/file1.css'), $repository->find('file1.css'));
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('file4.js'));
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/subdir/file5.js'), $repository->find('subdir/file5.js'));
$this->assertNull($repository->find('file5.css'));
}

Expand All @@ -36,33 +36,45 @@ public function testFindWithRelativePaths()
'dir2' => '',
], __DIR__.'/fixtures');

$this->assertSame(__DIR__.'/fixtures/dir1/file1.css', $repository->find('file1.css'));
$this->assertSame(__DIR__.'/fixtures/dir2/file4.js', $repository->find('file4.js'));
$this->assertSame(__DIR__.'/fixtures/dir2/subdir/file5.js', $repository->find('subdir/file5.js'));
$this->assertSame(realpath(__DIR__.'/fixtures/dir1/file1.css'), $repository->find('file1.css'));
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('file4.js'));
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/subdir/file5.js'), $repository->find('subdir/file5.js'));
$this->assertNull($repository->find('file5.css'));
}

public function testFindWithMovingPaths()
{
$repository = new AssetMapperRepository([
__DIR__.'/../Tests/fixtures/dir2' => '',
], __DIR__);

$this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('file4.js'));
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('subdir/../file4.js'));
}

public function testFindWithNamespaces()
{
$repository = new AssetMapperRepository([
'dir1' => 'dir1_namespace',
'dir2' => 'dir2_namespace',
], __DIR__.'/fixtures');

$this->assertSame(__DIR__.'/fixtures/dir1/file1.css', $repository->find('dir1_namespace/file1.css'));
$this->assertSame(__DIR__.'/fixtures/dir2/file4.js', $repository->find('dir2_namespace/file4.js'));
$this->assertSame(__DIR__.'/fixtures/dir2/subdir/file5.js', $repository->find('dir2_namespace/subdir/file5.js'));
$this->assertSame(realpath(__DIR__.'/fixtures/dir1/file1.css'), $repository->find('dir1_namespace/file1.css'));
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('dir2_namespace/file4.js'));
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/subdir/file5.js'), $repository->find('dir2_namespace/subdir/file5.js'));
// non-namespaced path does not work
$this->assertNull($repository->find('file4.js'));
}

public function testFindLogicalPath()
{
$repository = new AssetMapperRepository([
'dir1' => '',
'dir1' => 'some_namespace',
'dir2' => '',
], __DIR__.'/fixtures');
$this->assertSame('subdir/file5.js', $repository->findLogicalPath(__DIR__.'/fixtures/dir2/subdir/file5.js'));
$this->assertSame('some_namespace/file2.js', $repository->findLogicalPath(__DIR__.'/fixtures/dir1/file2.js'));
$this->assertSame('some_namespace/file2.js', $repository->findLogicalPath(__DIR__.'/../Tests/fixtures/dir1/file2.js'));
}

public function testAll()
Expand All @@ -83,8 +95,8 @@ public function testAll()
'already-abcdefVWXYZ0123456789.digested.css' => __DIR__.'/fixtures/dir2/already-abcdefVWXYZ0123456789.digested.css',
'file3.css' => __DIR__.'/fixtures/dir2/file3.css',
'file4.js' => __DIR__.'/fixtures/dir2/file4.js',
'subdir'.\DIRECTORY_SEPARATOR.'file5.js' => __DIR__.'/fixtures/dir2/subdir/file5.js',
'subdir'.\DIRECTORY_SEPARATOR.'file6.js' => __DIR__.'/fixtures/dir2/subdir/file6.js',
'subdir/file5.js' => __DIR__.'/fixtures/dir2/subdir/file5.js',
'subdir/file6.js' => __DIR__.'/fixtures/dir2/subdir/file6.js',
'test.gif.foo' => __DIR__.'/fixtures/dir3/test.gif.foo',
]);
$this->assertEquals($expectedAllAssets, array_map('realpath', $actualAllAssets));
Expand All @@ -109,16 +121,10 @@ public function testAllWithNamespaces()
'dir3_namespace/test.gif.foo' => __DIR__.'/fixtures/dir3/test.gif.foo',
];

$normalizedExpectedAllAssets = [];
foreach ($expectedAllAssets as $key => $val) {
$normalizedExpectedAllAssets[str_replace('/', \DIRECTORY_SEPARATOR, $key)] = realpath($val);
}
$normalizedExpectedAllAssets = array_map('realpath', $expectedAllAssets);

$actualAssets = $repository->all();
$normalizedActualAssets = [];
foreach ($actualAssets as $key => $val) {
$normalizedActualAssets[str_replace('/', \DIRECTORY_SEPARATOR, $key)] = realpath($val);
}
$normalizedActualAssets = array_map('realpath', $actualAssets);

$this->assertEquals($normalizedExpectedAllAssets, $normalizedActualAssets);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function testAssetsAreCompiled()
{
$application = new Application($this->kernel);

$command = $application->find('assetmap:compile');
$command = $application->find('asset-map:compile');
$tester = new CommandTester($command);
$res = $tester->execute([]);
$this->assertSame(0, $res);
Expand All @@ -59,6 +59,21 @@ public function testAssetsAreCompiled()
$finder->in($targetBuildDir)->files();
$this->assertCount(9, $finder);
$this->assertFileExists($targetBuildDir.'/manifest.json');

$expected = [
'file1.css',
'file2.js',
'file3.css',
'subdir/file6.js',
'subdir/file5.js',
'file4.js',
'already-abcdefVWXYZ0123456789.digested.css',
];
$actual = array_keys(json_decode(file_get_contents($targetBuildDir.'/manifest.json'), true));
sort($expected);
sort($actual);

$this->assertSame($expected, $actual);
$this->assertFileExists($targetBuildDir.'/importmap.json');
}
}
Loading
0