Repository: symfony/flex (php)

Issue:
[Bug] v1.13.0: composer package-type "metapackage" conflicts with PackageJsonSynchronizer
Steps to reproduce:
* `composer global require "symfony/flex"`
* `echo '{}' > package.json`
* `composer require "gaufrette/ftp-adapter"`

Output:
```
  [ErrorException]
  file_get_contents(/home/starke/flex-test/vendor/gaufrette/ftp-adapter/composer.json): failed to open stream: No such file or directory
```

Composer version 2.0.13 2021-04-27 13:11:08
symfony/flex v1.13.0

This does not occur with v1.12.2

Assumed Explanation:
The [package-type](https://getcomposer.org/doc/04-schema.md#type) doesn't "write anything to the filesystem". Therefore the composer.json can not be read, as it isn't present.

Pull request:
Fix looking for keywords
Fix #768

Patch:
diff --git a/src/Command/GenerateIdCommand.php b/src/Command/GenerateIdCommand.php
index 937cb9e..f262615 100644
--- a/src/Command/GenerateIdCommand.php
+++ b/src/Command/GenerateIdCommand.php
@@ -18,10 +18,9 @@ use Symfony\Component\Console\Style\SymfonyStyle;
 
 class GenerateIdCommand extends Command
 {
-    public function __construct(/* cannot be type-hinted */ $flex = null)
+    public function __construct()
     {
         // No-op to support downgrading to v1.12.x
-
         parent::__construct();
     }
 
diff --git a/src/Flex.php b/src/Flex.php
index a61fabd..f780452 100644
--- a/src/Flex.php
+++ b/src/Flex.php
@@ -593,9 +593,9 @@ class Flex implements PluginInterface, EventSubscriberInterface
         $synchronizer = new PackageJsonSynchronizer($rootDir, $vendorDir);
 
         if ($synchronizer->shouldSynchronize()) {
-            $packagesNames = array_column($this->composer->getLocker()->getLockData()['packages'] ?? [], 'name');
+            $lockData = $this->composer->getLocker()->getLockData();
 
-            if ($synchronizer->synchronize($packagesNames)) {
+            if ($synchronizer->synchronize($lockData['packages'] ?? []) || $synchronizer->synchronize($lockData['packages-dev'] ?? [])) {
                 $this->io->writeError('<info>Synchronizing package.json with PHP packages</>');
                 $this->io->writeError('<warning>Don\'t forget to run npm install --force or yarn install --force to refresh your JavaScript dependencies!</>');
                 $this->io->writeError('');
@@ -774,8 +774,8 @@ class Flex implements PluginInterface, EventSubscriberInterface
         file_put_contents($autoloadFile, <<<EOPHP
 <?php
 
-if (\PHP_VERSION_ID < $version) {
-    echo sprintf("Fatal Error: composer.lock was created for PHP version $platform or higher but the current PHP version is %d.%d.%d.\\n", PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION);
+if (PHP_VERSION_ID < $version) {
+    echo sprintf("Fatal Error: composer.lock was created for PHP version $platform or higher but the current PHP version is %d.%d.%s.\\n", PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION);
     exit(1);
 }
 $code
diff --git a/src/InformationOperation.php b/src/InformationOperation.php
index 7ed65db..f44baac 100644
--- a/src/InformationOperation.php
+++ b/src/InformationOperation.php
@@ -27,9 +27,6 @@ class InformationOperation implements OperationInterface
         return $this->package;
     }
 
-    /**
-     * {@inheritdoc}
-     */
     public function getJobType()
     {
         return 'information';
diff --git a/src/PackageJsonSynchronizer.php b/src/PackageJsonSynchronizer.php
index 36598e6..fdcfd7b 100644
--- a/src/PackageJsonSynchronizer.php
+++ b/src/PackageJsonSynchronizer.php
@@ -37,19 +37,19 @@ class PackageJsonSynchronizer
         return $this->rootDir && file_exists($this->rootDir.'/package.json');
     }
 
-    public function synchronize(array $packagesNames): bool
+    public function synchronize(array $phpPackages): bool
     {
         // Remove all links and add again only the existing packages
         $didAddLink = $this->removePackageJsonLinks((new JsonFile($this->rootDir.'/package.json'))->read());
 
-        foreach ($packagesNames as $packageName) {
-            $didAddLink = $this->addPackageJsonLink($packageName) || $didAddLink;
+        foreach ($phpPackages as $phpPackage) {
+            $didAddLink = $this->addPackageJsonLink($phpPackage) || $didAddLink;
         }
 
-        $this->registerPeerDependencies($packagesNames);
+        $this->registerPeerDependencies($phpPackages);
 
         // Register controllers and entrypoints in controllers.json
-        $this->registerWebpackResources($packagesNames);
+        $this->registerWebpackResources($phpPackages);
 
         return $didAddLink;
     }
@@ -76,14 +76,14 @@ class PackageJsonSynchronizer
         return $didRemoveLink;
     }
 
-    private function addPackageJsonLink(string $phpPackage): bool
+    private function addPackageJsonLink(array $phpPackage): bool
     {
         if (!$packageJson = $this->resolvePackageJson($phpPackage)) {
             return false;
         }
 
         $manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json'));
-        $manipulator->addSubNode('devDependencies', '@'.$phpPackage, 'file:'.substr($packageJson->getPath(), 1 + \strlen($this->rootDir), -13));
+        $manipulator->addSubNode('devDependencies', '@'.$phpPackage['name'], 'file:'.substr($packageJson->getPath(), 1 + \strlen($this->rootDir), -13));
 
         $content = json_decode($manipulator->getContents(), true);
 
@@ -112,10 +112,11 @@ class PackageJsonSynchronizer
             if (!$packageJson = $this->resolvePackageJson($phpPackage)) {
                 continue;
             }
+            $name = '@'.$phpPackage['name'];
 
             foreach ($packageJson->read()['symfony']['controllers'] ?? [] as $controllerName => $defaultConfig) {
                 // If the package has just been added (no config), add the default config provided by the package
-                if (!isset($previousControllersJson['controllers']['@'.$phpPackage][$controllerName])) {
+                if (!isset($previousControllersJson['controllers'][$name][$controllerName])) {
                     $config = [];
                     $config['enabled'] = $defaultConfig['enabled'];
                     $config['fetch'] = $defaultConfig['fetch'] ?? 'eager';
@@ -124,13 +125,13 @@ class PackageJsonSynchronizer
                         $config['autoimport'] = $defaultConfig['autoimport'];
                     }
 
-                    $newControllersJson['controllers']['@'.$phpPackage][$controllerName] = $config;
+                    $newControllersJson['controllers'][$name][$controllerName] = $config;
 
                     continue;
                 }
 
                 // Otherwise, the package exists: merge new config with uer config
-                $previousConfig = $previousControllersJson['controllers']['@'.$phpPackage][$controllerName];
+                $previousConfig = $previousControllersJson['controllers'][$name][$controllerName];
 
                 $config = [];
                 $config['enabled'] = $previousConfig['enabled'];
@@ -145,7 +146,7 @@ class PackageJsonSynchronizer
                     }
                 }
 
-                $newControllersJson['controllers']['@'.$phpPackage][$controllerName] = $config;
+                $newControllersJson['controllers'][$name][$controllerName] = $config;
             }
 
             foreach ($packageJson->read()['symfony']['entrypoints'] ?? [] as $entrypoint => $filename) {
@@ -191,11 +192,11 @@ class PackageJsonSynchronizer
         file_put_contents($this->rootDir.'/package.json', $manipulator->getContents());
     }
 
-    private function resolvePackageJson(string $phpPackage): ?JsonFile
+    private function resolvePackageJson(array $phpPackage): ?JsonFile
     {
-        $packageDir = $this->rootDir.'/'.$this->vendorDirname.'/'.$phpPackage;
+        $packageDir = $this->rootDir.'/'.$this->vendorDirname.'/'.$phpPackage['name'];
 
-        if (!\in_array('symfony-ux', json_decode(file_get_contents($packageDir.'/composer.json'), true)['keywords'] ?? [], true)) {
+        if (!\in_array('symfony-ux', $phpPackage['keywords'] ?? [], true)) {
             return null;
         }
 


Test patch:
diff --git a/tests/Fixtures/packageJson/vendor/symfony/existing-package/composer.json b/tests/Fixtures/packageJson/vendor/symfony/existing-package/composer.json
deleted file mode 100644
index e86dab1..0000000
--- a/tests/Fixtures/packageJson/vendor/symfony/existing-package/composer.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-    "keywords": [
-        "symfony-ux"
-    ]
-}
diff --git a/tests/Fixtures/packageJson/vendor/symfony/new-package/composer.json b/tests/Fixtures/packageJson/vendor/symfony/new-package/composer.json
deleted file mode 100644
index e86dab1..0000000
--- a/tests/Fixtures/packageJson/vendor/symfony/new-package/composer.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-    "keywords": [
-        "symfony-ux"
-    ]
-}
diff --git a/tests/PackageJsonSynchronizerTest.php b/tests/PackageJsonSynchronizerTest.php
index 72020bf..c0a4b92 100644
--- a/tests/PackageJsonSynchronizerTest.php
+++ b/tests/PackageJsonSynchronizerTest.php
@@ -64,7 +64,12 @@ class PackageJsonSynchronizerTest extends TestCase
 
     public function testSynchronizeExistingPackage()
     {
-        $this->synchronizer->synchronize(['symfony/existing-package']);
+        $this->synchronizer->synchronize([
+            [
+                'name' => 'symfony/existing-package',
+                'keywords' => ['symfony-ux'],
+            ],
+        ]);
 
         // Should keep existing package references and config
         $this->assertSame(
@@ -107,7 +112,16 @@ class PackageJsonSynchronizerTest extends TestCase
 
     public function testSynchronizeNewPackage()
     {
-        $this->synchronizer->synchronize(['symfony/existing-package', 'symfony/new-package']);
+        $this->synchronizer->synchronize([
+            [
+                'name' => 'symfony/existing-package',
+                'keywords' => ['symfony-ux'],
+            ],
+            [
+                'name' => 'symfony/new-package',
+                'keywords' => ['symfony-ux'],
+            ],
+        ]);
 
         // Should keep existing package references and config and add the new package, while keeping the formatting
         $this->assertSame(
@@ -159,7 +173,12 @@ class PackageJsonSynchronizerTest extends TestCase
 
     public function testArrayFormattingHasNotChanged()
     {
-        $this->synchronizer->synchronize(['symfony/existing-package']);
+        $this->synchronizer->synchronize([
+            [
+                'name' => 'symfony/existing-package',
+                'keywords' => ['symfony-ux'],
+            ],
+        ]);
 
         // Should keep existing array formatting
         $this->assertSame(