diff --git a/src/PackageJsonSynchronizer.php b/src/PackageJsonSynchronizer.php index 698894c5b..5daec3ba8 100644 --- a/src/PackageJsonSynchronizer.php +++ b/src/PackageJsonSynchronizer.php @@ -104,7 +104,7 @@ private function removeObsoletePackageJsonLinks(): bool foreach (['dependencies' => $jsDependencies, 'devDependencies' => $jsDevDependencies] as $key => $packages) { foreach ($packages as $name => $version) { - if ('@' !== $name[0] || 0 !== strpos($version, 'file:'.$this->vendorDir.'/') || false === strpos($version, '/assets')) { + if ('@' !== $name[0] || !str_starts_with($version, 'file:'.$this->vendorDir.'/') || !str_contains($version, '/assets')) { continue; } if (file_exists($this->rootDir.'/'.substr($version, 5).'/package.json')) { @@ -149,20 +149,36 @@ private function resolveImportMapPackages($phpPackage): array $dependencies = []; foreach ($packageJson->read()['symfony']['importmap'] ?? [] as $importMapName => $constraintConfig) { - if (\is_array($constraintConfig)) { - $constraint = $constraintConfig['version'] ?? []; - $package = $constraintConfig['package'] ?? $importMapName; - } else { + if (\is_string($constraintConfig)) { + // Matches string constraint, like "^3.0" or "path:%PACKAGE%/script.js" $constraint = $constraintConfig; $package = $importMapName; + $entrypoint = false; + } elseif (\is_array($constraintConfig)) { + // Matches array constraint, like {"version":"^3.0"} or {"version":"path:%PACKAGE%/script.js","entrypoint":true} + // Note that non-path assets can't be entrypoint + $constraint = $constraintConfig['version'] ?? ''; + $package = $constraintConfig['package'] ?? $importMapName; + $entrypoint = $constraintConfig['entrypoint'] ?? false; + } else { + throw new \InvalidArgumentException(\sprintf('Invalid constraint config for key "%s": "%s" given, array or string expected.', $importMapName, var_export($constraintConfig, true))); } - if (0 === strpos($constraint, 'path:')) { + // When "$constraintConfig" matches one of the following cases: + // - "entrypoint:%PACKAGE%/script.js" + // - {"version": "entrypoint:%PACKAGE%/script.js"} + if (str_starts_with($constraint, 'entrypoint:')) { + $entrypoint = true; + $constraint = substr_replace($constraint, 'path:', 0, \strlen('entrypoint:')); + } + + if (str_starts_with($constraint, 'path:')) { $path = substr($constraint, 5); $path = str_replace('%PACKAGE%', \dirname($packageJson->getPath()), $path); $dependencies[$importMapName] = [ 'path' => $path, + 'entrypoint' => $entrypoint, ]; continue; @@ -239,7 +255,7 @@ private function shouldUpdateConstraint(string $existingConstraint, string $cons } /** - * @param array $importMapEntries + * @param array $importMapEntries */ private function updateImportMap(array $importMapEntries): void { @@ -264,11 +280,15 @@ private function updateImportMap(array $importMapEntries): void continue; } - $this->io->writeError(sprintf('Updating package %s from %s to %s.', $name, $version, $versionConstraint)); + $this->io->writeError(\sprintf('Updating package %s from %s to %s.', $name, $version, $versionConstraint)); } if (isset($importMapEntry['path'])) { $arguments = [$name, '--path='.$importMapEntry['path']]; + if (isset($importMapEntry['entrypoint']) && true === $importMapEntry['entrypoint']) { + $arguments[] = '--entrypoint'; + } + $this->scriptExecutor->execute( 'symfony-cmd', 'importmap:require', @@ -293,7 +313,7 @@ private function updateImportMap(array $importMapEntries): void continue; } - throw new \InvalidArgumentException(sprintf('Invalid importmap entry: "%s".', var_export($importMapEntry, true))); + throw new \InvalidArgumentException(\sprintf('Invalid importmap entry: "%s".', var_export($importMapEntry, true))); } } diff --git a/tests/Fixtures/packageJson/vendor/symfony/importmap-invalid-constraint-package/assets/package.json b/tests/Fixtures/packageJson/vendor/symfony/importmap-invalid-constraint-package/assets/package.json new file mode 100644 index 000000000..ca57c76dc --- /dev/null +++ b/tests/Fixtures/packageJson/vendor/symfony/importmap-invalid-constraint-package/assets/package.json @@ -0,0 +1,7 @@ +{ + "symfony": { + "importmap": { + "@symfony/test": true + } + } +} diff --git a/tests/Fixtures/packageJson/vendor/symfony/new-package/assets/package.json b/tests/Fixtures/packageJson/vendor/symfony/new-package/assets/package.json index e4003ea6c..419528379 100644 --- a/tests/Fixtures/packageJson/vendor/symfony/new-package/assets/package.json +++ b/tests/Fixtures/packageJson/vendor/symfony/new-package/assets/package.json @@ -14,6 +14,11 @@ "@hotcake/foo": "^1.9.0", "@symfony/new-package": { "version": "path:%PACKAGE%/dist/loader.js" + }, + "@symfony/new-package/entry.js": "entrypoint:%PACKAGE%/entry.js", + "@symfony/new-package/entry2.js": { + "version": "path:%PACKAGE%/entry2.js", + "entrypoint": true } } }, diff --git a/tests/PackageJsonSynchronizerTest.php b/tests/PackageJsonSynchronizerTest.php index 7e6b27c0d..702a3881c 100644 --- a/tests/PackageJsonSynchronizerTest.php +++ b/tests/PackageJsonSynchronizerTest.php @@ -323,11 +323,16 @@ public function testSynchronizeAssetMapperNewPackage() file_put_contents($this->tempDir.'/importmap.php', 'tempDir.'/vendor/symfony/new-package/assets/dist/loader.js'; - $this->scriptExecutor->expects($this->exactly(2)) + $entrypointPath = $this->tempDir.'/vendor/symfony/new-package/assets/entry.js'; + $secondEntrypointPath = $this->tempDir.'/vendor/symfony/new-package/assets/entry2.js'; + + $this->scriptExecutor->expects($this->exactly(4)) ->method('execute') ->withConsecutive( ['symfony-cmd', 'importmap:require', ['@hotcake/foo@^1.9.0']], - ['symfony-cmd', 'importmap:require', ['@symfony/new-package', '--path='.$fileModulePath]] + ['symfony-cmd', 'importmap:require', ['@symfony/new-package', '--path='.$fileModulePath]], + ['symfony-cmd', 'importmap:require', ['@symfony/new-package/entry.js', '--path='.$entrypointPath, '--entrypoint']], + ['symfony-cmd', 'importmap:require', ['@symfony/new-package/entry2.js', '--path='.$secondEntrypointPath, '--entrypoint']], ); $this->synchronizer->synchronize([ @@ -396,14 +401,19 @@ public function testSynchronizeAssetMapperUpgradesPackageIfNeeded() 'version' => '1.8.0', ], ]; - file_put_contents($this->tempDir.'/importmap.php', sprintf('tempDir.'/importmap.php', \sprintf('tempDir.'/vendor/symfony/new-package/assets/dist/loader.js'; - $this->scriptExecutor->expects($this->exactly(2)) + $entrypointPath = $this->tempDir.'/vendor/symfony/new-package/assets/entry.js'; + $secondEntrypointPath = $this->tempDir.'/vendor/symfony/new-package/assets/entry2.js'; + + $this->scriptExecutor->expects($this->exactly(4)) ->method('execute') ->withConsecutive( ['symfony-cmd', 'importmap:require', ['@hotcake/foo@^1.9.0']], - ['symfony-cmd', 'importmap:require', ['@symfony/new-package', '--path='.$fileModulePath]] + ['symfony-cmd', 'importmap:require', ['@symfony/new-package', '--path='.$fileModulePath]], + ['symfony-cmd', 'importmap:require', ['@symfony/new-package/entry.js', '--path='.$entrypointPath, '--entrypoint']], + ['symfony-cmd', 'importmap:require', ['@symfony/new-package/entry2.js', '--path='.$secondEntrypointPath, '--entrypoint']] ); $this->synchronizer->synchronize([ @@ -421,14 +431,21 @@ public function testSynchronizeAssetMapperSkipsUpgradeIfAlreadySatisfied() // constraint in package.json is ^1.9.0 'version' => '1.9.1', ], + '@symfony/new-package/entry2.js' => [ + 'path' => './vendor/symfony/new-package/assets/entry2.js', + 'entrypoint' => true, + ], ]; - file_put_contents($this->tempDir.'/importmap.php', sprintf('tempDir.'/importmap.php', \sprintf('tempDir.'/vendor/symfony/new-package/assets/dist/loader.js'; - $this->scriptExecutor->expects($this->once()) + $entrypointPath = $this->tempDir.'/vendor/symfony/new-package/assets/entry.js'; + + $this->scriptExecutor->expects($this->exactly(2)) ->method('execute') ->withConsecutive( - ['symfony-cmd', 'importmap:require', ['@symfony/new-package', '--path='.$fileModulePath]] + ['symfony-cmd', 'importmap:require', ['@symfony/new-package', '--path='.$fileModulePath]], + ['symfony-cmd', 'importmap:require', ['@symfony/new-package/entry.js', '--path='.$entrypointPath, '--entrypoint']], ); $this->synchronizer->synchronize([ @@ -438,4 +455,19 @@ public function testSynchronizeAssetMapperSkipsUpgradeIfAlreadySatisfied() ], ]); } + + public function testExceptionWhenInvalidImportMapConstraint() + { + file_put_contents($this->tempDir.'/importmap.php', 'expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid constraint config for key "@symfony/test": "true" given, array or string expected.'); + + $this->synchronizer->synchronize([ + [ + 'name' => 'symfony/importmap-invalid-constraint-package', + 'keywords' => ['symfony-ux'], + ], + ]); + } }