Skip to content

Commit 7f48565

Browse files
weaverryanfabpot
authored andcommitted
[AssetMapper] Adding debug:assetmap command + normalize paths
1 parent 8703b18 commit 7f48565

File tree

7 files changed

+230
-28
lines changed

7 files changed

+230
-28
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\AssetMapper\AssetMapperInterface;
1818
use Symfony\Component\AssetMapper\AssetMapperRepository;
1919
use Symfony\Component\AssetMapper\Command\AssetMapperCompileCommand;
20+
use Symfony\Component\AssetMapper\Command\DebugAssetMapperCommand;
2021
use Symfony\Component\AssetMapper\Command\ImportMapExportCommand;
2122
use Symfony\Component\AssetMapper\Command\ImportMapRemoveCommand;
2223
use Symfony\Component\AssetMapper\Command\ImportMapRequireCommand;
@@ -70,6 +71,14 @@
7071
])
7172
->tag('console.command')
7273

74+
->set('asset_mapper.command.debug', DebugAssetMapperCommand::class)
75+
->args([
76+
service('asset_mapper'),
77+
service('asset_mapper.repository'),
78+
param('kernel.project_dir'),
79+
])
80+
->tag('console.command')
81+
7382
->set('asset_mapper_compiler', AssetMapperCompiler::class)
7483
->args([
7584
tagged_iterator('asset_mapper.compiler'),

src/Symfony/Component/AssetMapper/AssetMapperRepository.php

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public function find(string $logicalPath): ?string
5555

5656
$file = rtrim($path, '/').'/'.$localLogicalPath;
5757
if (file_exists($file)) {
58-
return $file;
58+
return realpath($file);
5959
}
6060
}
6161

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

6565
public function findLogicalPath(string $filesystemPath): ?string
6666
{
67+
if (!is_file($filesystemPath)) {
68+
return null;
69+
}
70+
71+
$filesystemPath = realpath($filesystemPath);
72+
6773
foreach ($this->getDirectories() as $path => $namespace) {
6874
if (!str_starts_with($filesystemPath, $path)) {
6975
continue;
7076
}
7177

7278
$logicalPath = substr($filesystemPath, \strlen($path));
79+
7380
if ('' !== $namespace) {
74-
$logicalPath = $namespace.'/'.$logicalPath;
81+
$logicalPath = $namespace.'/'.ltrim($logicalPath, '/\\');
7582
}
7683

77-
return ltrim($logicalPath, '/');
84+
return $this->normalizeLogicalPath($logicalPath);
7885
}
7986

8087
return null;
@@ -100,13 +107,22 @@ public function all(): array
100107
/** @var RecursiveDirectoryIterator $innerIterator */
101108
$innerIterator = $iterator->getInnerIterator();
102109
$logicalPath = ($namespace ? rtrim($namespace, '/').'/' : '').$innerIterator->getSubPathName();
110+
$logicalPath = $this->normalizeLogicalPath($logicalPath);
103111
$paths[$logicalPath] = $file->getPathname();
104112
}
105113
}
106114

107115
return $paths;
108116
}
109117

118+
/**
119+
* @internal
120+
*/
121+
public function allDirectories(): array
122+
{
123+
return $this->getDirectories();
124+
}
125+
110126
private function getDirectories(): array
111127
{
112128
$filesystem = new Filesystem();
@@ -120,13 +136,13 @@ private function getDirectories(): array
120136
if (!file_exists($path)) {
121137
throw new \InvalidArgumentException(sprintf('The asset mapper directory "%s" does not exist.', $path));
122138
}
123-
$this->absolutePaths[$path] = $namespace;
139+
$this->absolutePaths[realpath($path)] = $namespace;
124140

125141
continue;
126142
}
127143

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

131147
continue;
132148
}
@@ -136,4 +152,12 @@ private function getDirectories(): array
136152

137153
return $this->absolutePaths;
138154
}
155+
156+
/**
157+
* Normalize slashes to / for logical paths.
158+
*/
159+
private function normalizeLogicalPath(string $logicalPath): string
160+
{
161+
return ltrim(str_replace('\\', '/', $logicalPath), '/\\');
162+
}
139163
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
*
3232
* @author Ryan Weaver <ryan@symfonycasts.com>
3333
*/
34-
#[AsCommand(name: 'assetmap:compile', description: 'Compiles all mapped assets and writes them to the final public output directory.')]
34+
#[AsCommand(name: 'asset-map:compile', description: 'Compiles all mapped assets and writes them to the final public output directory.')]
3535
final class AssetMapperCompileCommand extends Command
3636
{
3737
public function __construct(
@@ -105,6 +105,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
105105
));
106106
}
107107

108-
return self::SUCCESS;
108+
return 0;
109109
}
110110
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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\Command;
13+
14+
use Symfony\Component\AssetMapper\AssetMapperInterface;
15+
use Symfony\Component\AssetMapper\AssetMapperRepository;
16+
use Symfony\Component\Console\Attribute\AsCommand;
17+
use Symfony\Component\Console\Command\Command;
18+
use Symfony\Component\Console\Input\InputInterface;
19+
use Symfony\Component\Console\Output\OutputInterface;
20+
use Symfony\Component\Console\Style\SymfonyStyle;
21+
22+
/**
23+
* Outputs all the assets in the asset mapper.
24+
*
25+
* @experimental
26+
*
27+
* @author Ryan Weaver <ryan@symfonycasts.com>
28+
*/
29+
#[AsCommand(name: 'debug:asset-map', description: 'Outputs all mapped assets.')]
30+
final class DebugAssetMapperCommand extends Command
31+
{
32+
private bool $didShortenPaths = false;
33+
34+
public function __construct(
35+
private readonly AssetMapperInterface $assetMapper,
36+
private readonly AssetMapperRepository $assetMapperRepository,
37+
private readonly string $projectDir,
38+
) {
39+
parent::__construct();
40+
}
41+
42+
protected function configure(): void
43+
{
44+
$this
45+
->addOption('full', null, null, 'Whether to show the full paths')
46+
->setHelp(<<<'EOT'
47+
The <info>%command.name%</info> command outputs all of the assets in
48+
asset mapper for debugging purposes.
49+
EOT
50+
);
51+
}
52+
53+
protected function execute(InputInterface $input, OutputInterface $output): int
54+
{
55+
$io = new SymfonyStyle($input, $output);
56+
57+
$allAssets = $this->assetMapper->allAssets();
58+
59+
$pathRows = [];
60+
foreach ($this->assetMapperRepository->allDirectories() as $path => $namespace) {
61+
$path = $this->relativizePath($path);
62+
if (!$input->getOption('full')) {
63+
$path = $this->shortenPath($path);
64+
}
65+
66+
$pathRows[] = [$path, $namespace];
67+
}
68+
$io->section('Asset Mapper Paths');
69+
$io->table(['Path', 'Namespace prefix'], $pathRows);
70+
71+
$rows = [];
72+
foreach ($allAssets as $asset) {
73+
$logicalPath = $asset->logicalPath;
74+
$sourcePath = $this->relativizePath($asset->getSourcePath());
75+
76+
if (!$input->getOption('full')) {
77+
$logicalPath = $this->shortenPath($logicalPath);
78+
$sourcePath = $this->shortenPath($sourcePath);
79+
}
80+
81+
$rows[] = [
82+
$logicalPath,
83+
$sourcePath,
84+
];
85+
}
86+
$io->section('Mapped Assets');
87+
$io->table(['Logical Path', 'Filesystem Path'], $rows);
88+
89+
if ($this->didShortenPaths) {
90+
$io->note('To see the full paths, re-run with the --full option.');
91+
}
92+
93+
return 0;
94+
}
95+
96+
private function relativizePath(string $path): string
97+
{
98+
return str_replace($this->projectDir.'/', '', $path);
99+
}
100+
101+
private function shortenPath($path): string
102+
{
103+
$limit = 50;
104+
105+
if (\strlen($path) <= $limit) {
106+
return $path;
107+
}
108+
109+
$this->didShortenPaths = true;
110+
$limit = floor(($limit - 3) / 2);
111+
112+
return substr($path, 0, $limit).'...'.substr($path, -$limit);
113+
}
114+
}

src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ public function testFindWithAbsolutePaths()
2323
__DIR__.'/fixtures/dir2' => '',
2424
], __DIR__);
2525

26-
$this->assertSame(__DIR__.'/fixtures/dir1/file1.css', $repository->find('file1.css'));
27-
$this->assertSame(__DIR__.'/fixtures/dir2/file4.js', $repository->find('file4.js'));
28-
$this->assertSame(__DIR__.'/fixtures/dir2/subdir/file5.js', $repository->find('subdir/file5.js'));
26+
$this->assertSame(realpath(__DIR__.'/fixtures/dir1/file1.css'), $repository->find('file1.css'));
27+
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('file4.js'));
28+
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/subdir/file5.js'), $repository->find('subdir/file5.js'));
2929
$this->assertNull($repository->find('file5.css'));
3030
}
3131

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

39-
$this->assertSame(__DIR__.'/fixtures/dir1/file1.css', $repository->find('file1.css'));
40-
$this->assertSame(__DIR__.'/fixtures/dir2/file4.js', $repository->find('file4.js'));
41-
$this->assertSame(__DIR__.'/fixtures/dir2/subdir/file5.js', $repository->find('subdir/file5.js'));
39+
$this->assertSame(realpath(__DIR__.'/fixtures/dir1/file1.css'), $repository->find('file1.css'));
40+
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('file4.js'));
41+
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/subdir/file5.js'), $repository->find('subdir/file5.js'));
4242
$this->assertNull($repository->find('file5.css'));
4343
}
4444

45+
public function testFindWithMovingPaths()
46+
{
47+
$repository = new AssetMapperRepository([
48+
__DIR__.'/../Tests/fixtures/dir2' => '',
49+
], __DIR__);
50+
51+
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('file4.js'));
52+
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('subdir/../file4.js'));
53+
}
54+
4555
public function testFindWithNamespaces()
4656
{
4757
$repository = new AssetMapperRepository([
4858
'dir1' => 'dir1_namespace',
4959
'dir2' => 'dir2_namespace',
5060
], __DIR__.'/fixtures');
5161

52-
$this->assertSame(__DIR__.'/fixtures/dir1/file1.css', $repository->find('dir1_namespace/file1.css'));
53-
$this->assertSame(__DIR__.'/fixtures/dir2/file4.js', $repository->find('dir2_namespace/file4.js'));
54-
$this->assertSame(__DIR__.'/fixtures/dir2/subdir/file5.js', $repository->find('dir2_namespace/subdir/file5.js'));
62+
$this->assertSame(realpath(__DIR__.'/fixtures/dir1/file1.css'), $repository->find('dir1_namespace/file1.css'));
63+
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/file4.js'), $repository->find('dir2_namespace/file4.js'));
64+
$this->assertSame(realpath(__DIR__.'/fixtures/dir2/subdir/file5.js'), $repository->find('dir2_namespace/subdir/file5.js'));
5565
// non-namespaced path does not work
5666
$this->assertNull($repository->find('file4.js'));
5767
}
5868

5969
public function testFindLogicalPath()
6070
{
6171
$repository = new AssetMapperRepository([
62-
'dir1' => '',
72+
'dir1' => 'some_namespace',
6373
'dir2' => '',
6474
], __DIR__.'/fixtures');
6575
$this->assertSame('subdir/file5.js', $repository->findLogicalPath(__DIR__.'/fixtures/dir2/subdir/file5.js'));
76+
$this->assertSame('some_namespace/file2.js', $repository->findLogicalPath(__DIR__.'/fixtures/dir1/file2.js'));
77+
$this->assertSame('some_namespace/file2.js', $repository->findLogicalPath(__DIR__.'/../Tests/fixtures/dir1/file2.js'));
6678
}
6779

6880
public function testAll()
@@ -83,8 +95,8 @@ public function testAll()
8395
'already-abcdefVWXYZ0123456789.digested.css' => __DIR__.'/fixtures/dir2/already-abcdefVWXYZ0123456789.digested.css',
8496
'file3.css' => __DIR__.'/fixtures/dir2/file3.css',
8597
'file4.js' => __DIR__.'/fixtures/dir2/file4.js',
86-
'subdir'.\DIRECTORY_SEPARATOR.'file5.js' => __DIR__.'/fixtures/dir2/subdir/file5.js',
87-
'subdir'.\DIRECTORY_SEPARATOR.'file6.js' => __DIR__.'/fixtures/dir2/subdir/file6.js',
98+
'subdir/file5.js' => __DIR__.'/fixtures/dir2/subdir/file5.js',
99+
'subdir/file6.js' => __DIR__.'/fixtures/dir2/subdir/file6.js',
88100
'test.gif.foo' => __DIR__.'/fixtures/dir3/test.gif.foo',
89101
]);
90102
$this->assertEquals($expectedAllAssets, array_map('realpath', $actualAllAssets));
@@ -109,16 +121,10 @@ public function testAllWithNamespaces()
109121
'dir3_namespace/test.gif.foo' => __DIR__.'/fixtures/dir3/test.gif.foo',
110122
];
111123

112-
$normalizedExpectedAllAssets = [];
113-
foreach ($expectedAllAssets as $key => $val) {
114-
$normalizedExpectedAllAssets[str_replace('/', \DIRECTORY_SEPARATOR, $key)] = realpath($val);
115-
}
124+
$normalizedExpectedAllAssets = array_map('realpath', $expectedAllAssets);
116125

117126
$actualAssets = $repository->all();
118-
$normalizedActualAssets = [];
119-
foreach ($actualAssets as $key => $val) {
120-
$normalizedActualAssets[str_replace('/', \DIRECTORY_SEPARATOR, $key)] = realpath($val);
121-
}
127+
$normalizedActualAssets = array_map('realpath', $actualAssets);
122128

123129
$this->assertEquals($normalizedExpectedAllAssets, $normalizedActualAssets);
124130
}

src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function testAssetsAreCompiled()
4040
{
4141
$application = new Application($this->kernel);
4242

43-
$command = $application->find('assetmap:compile');
43+
$command = $application->find('asset-map:compile');
4444
$tester = new CommandTester($command);
4545
$res = $tester->execute([]);
4646
$this->assertSame(0, $res);
@@ -59,6 +59,21 @@ public function testAssetsAreCompiled()
5959
$finder->in($targetBuildDir)->files();
6060
$this->assertCount(9, $finder);
6161
$this->assertFileExists($targetBuildDir.'/manifest.json');
62+
63+
$expected = [
64+
'file1.css',
65+
'file2.js',
66+
'file3.css',
67+
'subdir/file6.js',
68+
'subdir/file5.js',
69+
'file4.js',
70+
'already-abcdefVWXYZ0123456789.digested.css',
71+
];
72+
$actual = array_keys(json_decode(file_get_contents($targetBuildDir.'/manifest.json'), true));
73+
sort($expected);
74+
sort($actual);
75+
76+
$this->assertSame($expected, $actual);
6277
$this->assertFileExists($targetBuildDir.'/importmap.json');
6378
}
6479
}

0 commit comments

Comments
 (0)