From f4b80818a2aee49006573ae511446dd099d9afa9 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Sun, 21 Aug 2022 16:35:26 +0900
Subject: [PATCH] Issue #3272110 by Mile23, ravi.shankar, Spokje, xjm,
 andypost, joachim: Drupal 9 and 10's Drupal\Component composer.json files are
 totally out of date

---
 composer.json                                 |   3 +-
 composer/Composer.php                         |  12 +
 composer/Generator/ComponentGenerator.php     | 234 ++++++++++++++++++
 composer/Util/SemanticVersion.php             |  24 ++
 .../Drupal/Component/Annotation/composer.json |  42 ++--
 .../Drupal/Component/Assertion/composer.json  |  31 ++-
 .../Component/ClassFinder/composer.json       |  31 ++-
 .../Drupal/Component/Datetime/composer.json   |  36 +--
 .../DependencyInjection/composer.json         |  53 ++--
 core/lib/Drupal/Component/Diff/composer.json  |  31 ++-
 .../Drupal/Component/Discovery/composer.json  |  38 +--
 .../Component/EventDispatcher/composer.json   |  37 +--
 .../Drupal/Component/FileCache/composer.json  |  31 ++-
 .../Component/FileSecurity/composer.json      |  31 ++-
 .../Drupal/Component/FileSystem/composer.json |  31 ++-
 .../Component/FrontMatter/composer.json       |  36 +--
 .../Drupal/Component/Gettext/composer.json    |  41 +--
 core/lib/Drupal/Component/Graph/composer.json |  31 ++-
 .../Component/HttpFoundation/composer.json    |  33 ++-
 .../Drupal/Component/PhpStorage/composer.json |  36 +--
 .../lib/Drupal/Component/Plugin/composer.json |  41 +--
 .../Component/ProxyBuilder/composer.json      |  32 ++-
 .../lib/Drupal/Component/Render/composer.json |  36 +--
 .../Component/Serialization/composer.json     |  33 ++-
 .../Component/Transliteration/composer.json   |  33 ++-
 .../Drupal/Component/Utility/composer.json    |  31 ++-
 core/lib/Drupal/Component/Uuid/composer.json  |  36 +--
 .../Drupal/Component/Version/composer.json    |  31 ++-
 .../Component/ComponentsIsolatedBuildTest.php |  86 +++++++
 .../Component/ComponentsTaggedReleaseTest.php |  78 ++++++
 .../Composer/ComposerBuildTestBase.php        |  62 +++++
 .../Template/ComposerProjectTemplatesTest.php |  24 +-
 32 files changed, 1007 insertions(+), 358 deletions(-)
 create mode 100644 composer/Generator/ComponentGenerator.php
 create mode 100644 composer/Util/SemanticVersion.php
 create mode 100644 core/tests/Drupal/BuildTests/Composer/Component/ComponentsIsolatedBuildTest.php
 create mode 100644 core/tests/Drupal/BuildTests/Composer/Component/ComponentsTaggedReleaseTest.php
 create mode 100644 core/tests/Drupal/BuildTests/Composer/ComposerBuildTestBase.php

diff --git a/composer.json b/composer.json
index 722912c2103b..f5475d93abc6 100644
--- a/composer.json
+++ b/composer.json
@@ -108,7 +108,8 @@
             "@composer update phpunit/phpunit --with-dependencies --no-progress"
         ],
         "post-update-cmd": [
-            "Drupal\\Composer\\Composer::generateMetapackages"
+            "Drupal\\Composer\\Composer::generateMetapackages",
+            "Drupal\\Composer\\Composer::generateComponentPackages"
         ],
         "phpcs": "phpcs --standard=core/phpcs.xml.dist --",
         "phpcbf": "phpcbf --standard=core/phpcs.xml.dist --"
diff --git a/composer/Composer.php b/composer/Composer.php
index 040e8dd73c16..bdf53cfad137 100644
--- a/composer/Composer.php
+++ b/composer/Composer.php
@@ -6,6 +6,7 @@
 use Composer\Script\Event;
 use Composer\Semver\Comparator;
 use Composer\Semver\VersionParser;
+use Drupal\Composer\Generator\ComponentGenerator;
 use Drupal\Composer\Generator\PackageGenerator;
 use Symfony\Component\Finder\Finder;
 
@@ -30,6 +31,17 @@ public static function generateMetapackages(Event $event): void {
     $generator->generate($event->getIO(), getcwd());
   }
 
+  /**
+   * Update component packages whenever composer.lock is updated.
+   *
+   * @param \Composer\Script\Event $event
+   *   The Composer event.
+   */
+  public static function generateComponentPackages(Event $event): void {
+    $generator = new ComponentGenerator();
+    $generator->generate($event, getcwd());
+  }
+
   /**
    * Set the version of Drupal; used in release process and by the test suite.
    *
diff --git a/composer/Generator/ComponentGenerator.php b/composer/Generator/ComponentGenerator.php
new file mode 100644
index 000000000000..c82468cbe7b0
--- /dev/null
+++ b/composer/Generator/ComponentGenerator.php
@@ -0,0 +1,234 @@
+<?php
+
+namespace Drupal\Composer\Generator;
+
+use Composer\IO\IOInterface;
+use Composer\Semver\VersionParser;
+use Composer\Script\Event;
+use Composer\Util\Filesystem;
+use Drupal\Composer\Composer;
+use Drupal\Composer\Generator\Util\DrupalCoreComposer;
+use Drupal\Composer\Util\SemanticVersion;
+use Symfony\Component\Finder\Finder;
+
+/**
+ * Reconciles Drupal component dependencies with core.
+ */
+class ComponentGenerator {
+
+  /**
+   * Relative path from Drupal root to the component directory.
+   *
+   * @var string
+   */
+  protected static $relativeComponentPath = 'core/lib/Drupal/Component';
+
+  /**
+   * Full path to the component directory.
+   *
+   * @var string
+   */
+  protected $componentBaseDir;
+
+  /**
+   * Data from drupal/drupal's composer.json file.
+   *
+   * @var \Drupal\Composer\Generator\Util\DrupalCoreComposer
+   */
+  protected $drupalProjectInfo;
+
+  /**
+   * Data from drupal/core's composer.json file.
+   *
+   * @var \Drupal\Composer\Generator\Util\DrupalCoreComposer
+   */
+  protected $drupalCoreInfo;
+
+  /**
+   * ComponentGenerator constructor.
+   */
+  public function __construct() {
+    $this->componentBaseDir = dirname(__DIR__, 2) . '/' . static::$relativeComponentPath;
+  }
+
+  /**
+   * Find all the composer.json files for components.
+   *
+   * @return \Symfony\Component\Finder\Finder
+   *   A Finder object with all the composer.json files for components.
+   */
+  public function getComponentPathsFinder(): Finder {
+    $composer_json_finder = new Finder();
+    $composer_json_finder->name('composer.json')
+      ->in($this->componentBaseDir)
+      ->ignoreUnreadableDirs()
+      ->depth(1);
+    return $composer_json_finder;
+  }
+
+  /**
+   * Reconcile Drupal's components whenever composer.lock is updated.
+   *
+   * @param \Composer\Script\Event $event
+   *   The Composer event.
+   * @param string $base_dir
+   *   Directory where drupal/drupal repository is located.
+   */
+  public function generate(Event $event, string $base_dir): void {
+    $io = $event->getIO();
+    // General information from drupal/drupal and drupal/core composer.json
+    // and composer.lock files.
+    $this->drupalProjectInfo = DrupalCoreComposer::createFromPath($base_dir);
+    $this->drupalCoreInfo = DrupalCoreComposer::createFromPath($base_dir . '/core');
+
+    $changed = FALSE;
+    /** @var \Symfony\Component\Finder\SplFileInfo $component_composer_json */
+    foreach ($this->getComponentPathsFinder()->getIterator() as $component_composer_json) {
+      $changed |= $this->generateComponentPackage($event, $component_composer_json->getRelativePathname());
+    }
+
+    // Remind the user not to miss files in a patch.
+    if ($changed) {
+      $io->write("If you make a patch, ensure that the files above are included.");
+    }
+  }
+
+  /**
+   * Generate the component JSON files.
+   *
+   * @param \Composer\Script\Event $event
+   *   The Composer event.
+   * @param string $component_pathname
+   *   Relative path to the composer.json file for a component.
+   *
+   * @return bool
+   *   TRUE if the generated component package is different from what is on
+   *   disk.
+   */
+  protected function generateComponentPackage(Event $event, string $component_pathname): bool {
+    $io = $event->getIO();
+    $composer_json_path = $this->componentBaseDir . '/' . $component_pathname;
+    $original_composer_json = file_exists($composer_json_path) ? file_get_contents($composer_json_path) : '';
+
+    // Modify the original data.
+    $composer_json_data = $this->getPackage($io, $original_composer_json);
+    $updated_composer_json = static::encode($composer_json_data);
+
+    // Exit early if nothing changed.
+    if (trim($original_composer_json, " \t\r\0\x0B") === trim($updated_composer_json, " \t\r\0\x0B")) {
+      return FALSE;
+    }
+
+    // Warn the user that a component file has been updated.
+    $display_path = static::$relativeComponentPath . '/' . $component_pathname;
+    $io->write("Updated component file <info>$display_path</info>.");
+
+    // Write the composer.json file back to disk.
+    $fs = new Filesystem();
+    $fs->ensureDirectoryExists(dirname($composer_json_path));
+    file_put_contents($composer_json_path, $updated_composer_json);
+
+    return TRUE;
+  }
+
+  /**
+   * Reconcile component dependencies with core.
+   *
+   * @param \Composer\IO\IOInterface $io
+   *   IO object for messages to the user.
+   * @param string $original_json
+   *   Contents of the component's composer.json file.
+   *
+   * @return array
+   *   Structured data to be turned back into JSON.
+   */
+  protected function getPackage(IOInterface $io, string $original_json): array {
+    $original_data = json_decode($original_json, TRUE);
+    $package_data = array_merge($original_data, $this->initialPackageMetadata());
+
+    $core_info = $this->drupalCoreInfo->rootComposerJson();
+
+    $stability = VersionParser::parseStability(\Drupal::VERSION);
+
+    // List of packages which we didn't find in either core requirement.
+    $not_in_core = [];
+
+    // Traverse required packages.
+    foreach (array_keys($original_data['require'] ?? []) as $package_name) {
+      // Reconcile locked constraints from drupal/drupal. We might have a locked
+      // version of a dependency that's not present in drupal/core.
+      if ($info = $this->drupalProjectInfo->packageLockInfo($package_name)) {
+        $package_data['require'][$package_name] = $info['version'];
+      }
+      // The package wasn't in the lock file, which means we need to tell the
+      // user. But there are some packages we want to exclude from this list.
+      elseif ($package_name !== 'php' && (strpos($package_name, 'drupal/core-') === FALSE)) {
+        $not_in_core[$package_name] = $package_name;
+      }
+
+      // Reconcile looser constraints from drupal/core, and we're totally OK
+      // with over-writing the locked ones from above.
+      if ($constraint = $core_info['require'][$package_name] ?? FALSE) {
+        $package_data['require'][$package_name] = $constraint;
+      }
+
+      // Reconcile dependencies on other Drupal components, so we can set the
+      // constraint to our current version.
+      if (strpos($package_name, 'drupal/core-') !== FALSE) {
+        if ($stability === 'stable') {
+          // Set the constraint to ^maj.min.
+          $package_data['require'][$package_name] = SemanticVersion::majorMinorConstraint(\Drupal::VERSION);
+        }
+        else {
+          // For non-stable releases, set the constraint to the branch version.
+          $package_data['require'][$package_name] = Composer::drupalVersionBranch();
+          // Also for non-stable releases which depend on another component,
+          // set the minimum stability. We do this so we can test build the
+          // components. Minimum-stability is otherwise ignored for packages
+          // which aren't the root package, so for any other purpose, this is
+          // unneeded.
+          $package_data['minimum-stability'] = $stability;
+        }
+      }
+    }
+    if ($not_in_core) {
+      $io->error($package_data['name'] . ' requires packages not present in drupal/drupal: ' . implode(', ', $not_in_core));
+    }
+
+    return $package_data;
+  }
+
+  /**
+   * Utility function to encode package json in a consistent way.
+   *
+   * @param array $composer_json_data
+   *   Data to encode into a json string.
+   *
+   * @return string
+   *   Encoded version of provided json data.
+   */
+  public static function encode(array $composer_json_data): string {
+    return json_encode($composer_json_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
+  }
+
+  /**
+   * Common default metadata for all components.
+   *
+   * @return array
+   *   An array containing the common default metadata for all components.
+   */
+  protected function initialPackageMetadata(): array {
+    return [
+      'extra' => [
+        '_readme' => [
+          'This file was partially generated automatically. See: https://www.drupal.org/node/3293830',
+        ],
+      ],
+      // Always reconcile PHP version.
+      'require' => [
+        'php' => '>=7.3.0',
+      ],
+    ];
+  }
+
+}
diff --git a/composer/Util/SemanticVersion.php b/composer/Util/SemanticVersion.php
new file mode 100644
index 000000000000..c42e1a6f1c06
--- /dev/null
+++ b/composer/Util/SemanticVersion.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\Composer\Util;
+
+/**
+ * Utility methods for manipulating semantic versions.
+ */
+class SemanticVersion {
+
+  /**
+   * Given a version, generate a loose ^major.minor constraint.
+   *
+   * @param string $version
+   *   Semantic version string. Example: 9.5.0-beta23.
+   *
+   * @return string
+   *   Constraint string for major and minor. Example: ^9.5
+   */
+  public static function majorMinorConstraint(string $version): string {
+    preg_match('/^(\d+)\.(\d+)\.\d+/', $version, $matches);
+    return '^' . $matches[1] . '.' . $matches[2];
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Annotation/composer.json b/core/lib/Drupal/Component/Annotation/composer.json
index c252a2f97148..a631867d4367 100644
--- a/core/lib/Drupal/Component/Annotation/composer.json
+++ b/core/lib/Drupal/Component/Annotation/composer.json
@@ -1,19 +1,27 @@
 {
-  "name": "drupal/core-annotation",
-  "description": "Annotation discovery and implementation of plugins.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0",
-    "doctrine/annotations": "^1.4",
-    "drupal/core-file-cache": "^8.8",
-    "drupal/core-plugin": "^8.8",
-    "drupal/core-utility": "^8.8"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Annotation\\": ""
-    }
-  }
+    "name": "drupal/core-annotation",
+    "description": "Annotation discovery and implementation of plugins.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0",
+        "doctrine/annotations": "^1.13",
+        "drupal/core-file-cache": "10.1.x-dev",
+        "drupal/core-plugin": "10.1.x-dev",
+        "drupal/core-utility": "10.1.x-dev"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Annotation\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
+    },
+    "minimum-stability": "dev"
 }
diff --git a/core/lib/Drupal/Component/Assertion/composer.json b/core/lib/Drupal/Component/Assertion/composer.json
index 661654b13362..49e2e0e75bbc 100644
--- a/core/lib/Drupal/Component/Assertion/composer.json
+++ b/core/lib/Drupal/Component/Assertion/composer.json
@@ -1,15 +1,22 @@
 {
-  "name": "drupal/core-assertion",
-  "description": "Provides helper functionality for runtime assertions.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Assertion\\": ""
+    "name": "drupal/core-assertion",
+    "description": "Provides helper functionality for runtime assertions.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Assertion\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/ClassFinder/composer.json b/core/lib/Drupal/Component/ClassFinder/composer.json
index 9efde0acdda7..d31e53402777 100644
--- a/core/lib/Drupal/Component/ClassFinder/composer.json
+++ b/core/lib/Drupal/Component/ClassFinder/composer.json
@@ -1,15 +1,22 @@
 {
-  "name": "drupal/core-class-finder",
-  "description": "This class provides a class finding utility.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\ClassFinder\\": ""
+    "name": "drupal/core-class-finder",
+    "description": "This class provides a class finding utility.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\ClassFinder\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/Datetime/composer.json b/core/lib/Drupal/Component/Datetime/composer.json
index b06d7433a308..7a1429803d6e 100644
--- a/core/lib/Drupal/Component/Datetime/composer.json
+++ b/core/lib/Drupal/Component/Datetime/composer.json
@@ -1,16 +1,24 @@
 {
-  "name": "drupal/core-datetime",
-  "description": "This class wraps the PHP DateTime class with more flexible initialization parameters, allowing a date to be created from an existing date object, a timestamp, a string with an unknown format, a string with a known format, or an array of date parts. It also adds an errors array and a __toString() method to the date object.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0",
-    "drupal/core-utility": "^8.8"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Datetime\\": ""
-    }
-  }
+    "name": "drupal/core-datetime",
+    "description": "This class wraps the PHP DateTime class with more flexible initialization parameters, allowing a date to be created from an existing date object, a timestamp, a string with an unknown format, a string with a known format, or an array of date parts. It also adds an errors array and a __toString() method to the date object.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0",
+        "drupal/core-utility": "10.1.x-dev"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Datetime\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
+    },
+    "minimum-stability": "dev"
 }
diff --git a/core/lib/Drupal/Component/DependencyInjection/composer.json b/core/lib/Drupal/Component/DependencyInjection/composer.json
index e370fa3ed783..edf38af9998f 100644
--- a/core/lib/Drupal/Component/DependencyInjection/composer.json
+++ b/core/lib/Drupal/Component/DependencyInjection/composer.json
@@ -1,26 +1,33 @@
 {
-  "name": "drupal/core-dependency-injection",
-  "description": "Dependency Injection container optimized for Drupal's needs.",
-  "keywords": ["drupal", "dependency injection"],
-  "type": "library",
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "support": {
-    "issues": "https://www.drupal.org/project/issues/drupal",
-    "irc": "irc://irc.freenode.net/drupal-contribute",
-    "source": "https://www.drupal.org/project/drupal/git-instructions"
-  },
-  "require": {
-    "php": ">=7.3.0",
-    "symfony/dependency-injection": "^4.4",
-    "symfony/service-contracts": "^1.1|^2"
-  },
-  "suggest": {
-    "symfony/expression-language": "For using expressions in service container configuration"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\DependencyInjection\\": ""
+    "name": "drupal/core-dependency-injection",
+    "description": "Dependency Injection container optimized for Drupal's needs.",
+    "keywords": [
+        "drupal",
+        "dependency injection"
+    ],
+    "type": "library",
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "support": {
+        "issues": "https://www.drupal.org/project/issues/drupal",
+        "source": "https://www.drupal.org/project/drupal/git-instructions"
+    },
+    "require": {
+        "php": ">=8.1.0",
+        "symfony/dependency-injection": "^6.1",
+        "symfony/service-contracts": "v3.1.0"
+    },
+    "suggest": {
+        "symfony/expression-language": "For using expressions in service container configuration"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\DependencyInjection\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/Diff/composer.json b/core/lib/Drupal/Component/Diff/composer.json
index 7af1986afd43..972c5e2f771b 100644
--- a/core/lib/Drupal/Component/Diff/composer.json
+++ b/core/lib/Drupal/Component/Diff/composer.json
@@ -1,15 +1,22 @@
 {
-  "name": "drupal/core-diff",
-  "description": "Diff.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Diff\\": ""
+    "name": "drupal/core-diff",
+    "description": "Diff.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Diff\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/Discovery/composer.json b/core/lib/Drupal/Component/Discovery/composer.json
index 59fe78eeceee..5d3ea3487c10 100644
--- a/core/lib/Drupal/Component/Discovery/composer.json
+++ b/core/lib/Drupal/Component/Discovery/composer.json
@@ -1,17 +1,25 @@
 {
-  "name": "drupal/core-discovery",
-  "description": "Discovery.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0",
-    "drupal/core-file-cache": "^8.8",
-    "drupal/core-serialization": "^8.8"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Discovery\\": ""
-    }
-  }
+    "name": "drupal/core-discovery",
+    "description": "Discovery.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0",
+        "drupal/core-file-cache": "10.1.x-dev",
+        "drupal/core-serialization": "10.1.x-dev"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Discovery\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
+    },
+    "minimum-stability": "dev"
 }
diff --git a/core/lib/Drupal/Component/EventDispatcher/composer.json b/core/lib/Drupal/Component/EventDispatcher/composer.json
index 3574908ffbd7..fadafccc8195 100644
--- a/core/lib/Drupal/Component/EventDispatcher/composer.json
+++ b/core/lib/Drupal/Component/EventDispatcher/composer.json
@@ -1,18 +1,25 @@
 {
-  "name": "drupal/core-event-dispatcher",
-  "description": "EventDispatcher.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0",
-    "symfony/dependency-injection": "^4.4",
-    "symfony/event-dispatcher": "^4.4",
-    "symfony/event-dispatcher-contracts": "^1.1"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\EventDispatcher\\": ""
+    "name": "drupal/core-event-dispatcher",
+    "description": "EventDispatcher.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0",
+        "symfony/dependency-injection": "^6.1",
+        "symfony/event-dispatcher": "^6.1",
+        "symfony/event-dispatcher-contracts": "v3.1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\EventDispatcher\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/FileCache/composer.json b/core/lib/Drupal/Component/FileCache/composer.json
index 68f202b57a4d..c262143bb5c8 100644
--- a/core/lib/Drupal/Component/FileCache/composer.json
+++ b/core/lib/Drupal/Component/FileCache/composer.json
@@ -1,15 +1,22 @@
 {
-  "name": "drupal/core-file-cache",
-  "description": "FileCache.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\FileCache\\": ""
+    "name": "drupal/core-file-cache",
+    "description": "FileCache.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\FileCache\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/FileSecurity/composer.json b/core/lib/Drupal/Component/FileSecurity/composer.json
index 33a11ae6ef2f..880de76a6c58 100644
--- a/core/lib/Drupal/Component/FileSecurity/composer.json
+++ b/core/lib/Drupal/Component/FileSecurity/composer.json
@@ -1,15 +1,22 @@
 {
-  "name": "drupal/core-file-security",
-  "description": "FileSecurity.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\FileSecurity\\": ""
+    "name": "drupal/core-file-security",
+    "description": "FileSecurity.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\FileSecurity\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/FileSystem/composer.json b/core/lib/Drupal/Component/FileSystem/composer.json
index 41fa20509495..975c5d554a34 100644
--- a/core/lib/Drupal/Component/FileSystem/composer.json
+++ b/core/lib/Drupal/Component/FileSystem/composer.json
@@ -1,15 +1,22 @@
 {
-  "name": "drupal/core-filesystem",
-  "description": "FileSystem.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\FileSystem\\": ""
+    "name": "drupal/core-filesystem",
+    "description": "FileSystem.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\FileSystem\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/FrontMatter/composer.json b/core/lib/Drupal/Component/FrontMatter/composer.json
index 15a0b081eca1..2fc1da7a757f 100644
--- a/core/lib/Drupal/Component/FrontMatter/composer.json
+++ b/core/lib/Drupal/Component/FrontMatter/composer.json
@@ -1,16 +1,24 @@
 {
-  "name": "drupal/core-front-matter",
-  "description": "Component for parsing front matter from a source.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0",
-    "drupal/core-serialization": "^8.8"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\FrontMatter\\": ""
-    }
-  }
+    "name": "drupal/core-front-matter",
+    "description": "Component for parsing front matter from a source.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0",
+        "drupal/core-serialization": "10.1.x-dev"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\FrontMatter\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
+    },
+    "minimum-stability": "dev"
 }
diff --git a/core/lib/Drupal/Component/Gettext/composer.json b/core/lib/Drupal/Component/Gettext/composer.json
index 7320270e3852..9b6a7f3e8bb3 100644
--- a/core/lib/Drupal/Component/Gettext/composer.json
+++ b/core/lib/Drupal/Component/Gettext/composer.json
@@ -1,20 +1,25 @@
 {
-  "name": "drupal/core-gettext",
-  "description": "PHP library for reading PO files.",
-  "type": "library",
-  "license": "GPL-2.0-or-later",
-  "support": {
-    "issues": "https://www.drupal.org/project/issues/drupal",
-    "irc": "irc://irc.freenode.net/drupal-contribute",
-    "source": "https://www.drupal.org/project/drupal/git-instructions"
-  },
-  "require": {
-    "php": ">=7.3.0",
-    "drupal/core-render": "^9.4"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Gettext\\": ""
-    }
-  }
+    "name": "drupal/core-gettext",
+    "description": "PHP library for reading PO files.",
+    "type": "library",
+    "license": "GPL-2.0-or-later",
+    "support": {
+        "issues": "https://www.drupal.org/project/issues/drupal",
+        "source": "https://www.drupal.org/project/drupal/git-instructions"
+    },
+    "require": {
+        "php": ">=8.1.0",
+        "drupal/core-render": "10.1.x-dev"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Gettext\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
+    },
+    "minimum-stability": "dev"
 }
diff --git a/core/lib/Drupal/Component/Graph/composer.json b/core/lib/Drupal/Component/Graph/composer.json
index dbcc0eebb15a..c6c4e2b02ce2 100644
--- a/core/lib/Drupal/Component/Graph/composer.json
+++ b/core/lib/Drupal/Component/Graph/composer.json
@@ -1,15 +1,22 @@
 {
-  "name": "drupal/core-graph",
-  "description": "Graph.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Graph\\": ""
+    "name": "drupal/core-graph",
+    "description": "Graph.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Graph\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/HttpFoundation/composer.json b/core/lib/Drupal/Component/HttpFoundation/composer.json
index 0c481d6011fe..e2d1c69354b2 100644
--- a/core/lib/Drupal/Component/HttpFoundation/composer.json
+++ b/core/lib/Drupal/Component/HttpFoundation/composer.json
@@ -1,16 +1,23 @@
 {
-  "name": "drupal/core-http-foundation",
-  "description": "HttpFoundation.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0",
-    "symfony/http-foundation": "^4.4"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\HttpFoundation\\": ""
+    "name": "drupal/core-http-foundation",
+    "description": "HttpFoundation.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0",
+        "symfony/http-foundation": "^6.1.1"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\HttpFoundation\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/PhpStorage/composer.json b/core/lib/Drupal/Component/PhpStorage/composer.json
index 776426ba35b6..603314d00f8e 100644
--- a/core/lib/Drupal/Component/PhpStorage/composer.json
+++ b/core/lib/Drupal/Component/PhpStorage/composer.json
@@ -1,16 +1,24 @@
 {
-  "name": "drupal/core-php-storage",
-  "description": "PhpStorage.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0",
-    "drupal/core-file-security": "^8.8"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\PhpStorage\\": ""
-    }
-  }
+    "name": "drupal/core-php-storage",
+    "description": "PhpStorage.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0",
+        "drupal/core-file-security": "10.1.x-dev"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\PhpStorage\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
+    },
+    "minimum-stability": "dev"
 }
diff --git a/core/lib/Drupal/Component/Plugin/composer.json b/core/lib/Drupal/Component/Plugin/composer.json
index 8814a8ef3a9d..6f4cdbae6425 100644
--- a/core/lib/Drupal/Component/Plugin/composer.json
+++ b/core/lib/Drupal/Component/Plugin/composer.json
@@ -1,19 +1,28 @@
 {
-  "name": "drupal/core-plugin",
-  "description": "Base building block for a scalable and extensible plug-in system for PHP components and application framework extensions.",
-  "keywords": ["drupal", "plugin", "plugins"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0",
-    "symfony/validator": "^4.4"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Plugin\\": ""
+    "name": "drupal/core-plugin",
+    "description": "Base building block for a scalable and extensible plug-in system for PHP components and application framework extensions.",
+    "keywords": [
+        "drupal",
+        "plugin",
+        "plugins"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0",
+        "symfony/validator": "^6.1"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Plugin\\": ""
+        }
+    },
+    "suggest": {
+        "symfony/validator": "Leveraged in the use of context aware plugins."
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  },
-  "suggest": {
-    "symfony/validator": "Leveraged in the use of context aware plugins."
-  }
 }
diff --git a/core/lib/Drupal/Component/ProxyBuilder/composer.json b/core/lib/Drupal/Component/ProxyBuilder/composer.json
index 9b798e9265d1..68b9be598cd8 100644
--- a/core/lib/Drupal/Component/ProxyBuilder/composer.json
+++ b/core/lib/Drupal/Component/ProxyBuilder/composer.json
@@ -1,15 +1,23 @@
 {
-  "name": "drupal/core-proxy-builder",
-  "description": "Provides a lightweight mechanism to provide lazy loaded proxies.",
-  "keywords": ["drupal", "proxy"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\ProxyBuilder\\": ""
+    "name": "drupal/core-proxy-builder",
+    "description": "Provides a lightweight mechanism to provide lazy loaded proxies.",
+    "keywords": [
+        "drupal",
+        "proxy"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\ProxyBuilder\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/Render/composer.json b/core/lib/Drupal/Component/Render/composer.json
index 5b35fcb2a3fd..ba1f47c04bbb 100644
--- a/core/lib/Drupal/Component/Render/composer.json
+++ b/core/lib/Drupal/Component/Render/composer.json
@@ -1,16 +1,24 @@
 {
-  "name": "drupal/core-render",
-  "description": "Renders placeholder variables for HTML and plain-text display.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0",
-    "drupal/core-utility": "^8.8"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Render\\": ""
-    }
-  }
+    "name": "drupal/core-render",
+    "description": "Renders placeholder variables for HTML and plain-text display.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0",
+        "drupal/core-utility": "10.1.x-dev"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Render\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
+    },
+    "minimum-stability": "dev"
 }
diff --git a/core/lib/Drupal/Component/Serialization/composer.json b/core/lib/Drupal/Component/Serialization/composer.json
index 7b40c55ddc31..52fb12a5d2ae 100644
--- a/core/lib/Drupal/Component/Serialization/composer.json
+++ b/core/lib/Drupal/Component/Serialization/composer.json
@@ -1,16 +1,23 @@
 {
-  "name": "drupal/core-serialization",
-  "description": "Serialization.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0",
-    "symfony/yaml": "^4.4"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Serialization\\": ""
+    "name": "drupal/core-serialization",
+    "description": "Serialization.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0",
+        "symfony/yaml": "^6.1"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Serialization\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/Transliteration/composer.json b/core/lib/Drupal/Component/Transliteration/composer.json
index abdce4fc813c..7e42ba488ab7 100644
--- a/core/lib/Drupal/Component/Transliteration/composer.json
+++ b/core/lib/Drupal/Component/Transliteration/composer.json
@@ -1,16 +1,23 @@
 {
-  "name": "drupal/core-transliteration",
-  "description": "Transliteration.",
-  "type": "library",
-  "license": "GPL-2.0-or-later",
-  "support": {
-    "issues": "https://www.drupal.org/project/issues/drupal",
-    "irc": "irc://irc.freenode.net/drupal-contribute",
-    "source": "https://www.drupal.org/project/drupal/git-instructions"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Transliteration\\": ""
+    "name": "drupal/core-transliteration",
+    "description": "Transliteration.",
+    "type": "library",
+    "license": "GPL-2.0-or-later",
+    "support": {
+        "issues": "https://www.drupal.org/project/issues/drupal",
+        "source": "https://www.drupal.org/project/drupal/git-instructions"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Transliteration\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
+    },
+    "require": {
+        "php": ">=8.1.0"
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/Utility/composer.json b/core/lib/Drupal/Component/Utility/composer.json
index be455d0f4f00..da6c4a096be8 100644
--- a/core/lib/Drupal/Component/Utility/composer.json
+++ b/core/lib/Drupal/Component/Utility/composer.json
@@ -1,15 +1,22 @@
 {
-  "name": "drupal/core-utility",
-  "description": "Mostly static utility classes for string, xss, array, image, and other commonly needed manipulations.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Utility\\": ""
+    "name": "drupal/core-utility",
+    "description": "Mostly static utility classes for string, xss, array, image, and other commonly needed manipulations.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Utility\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/Uuid/composer.json b/core/lib/Drupal/Component/Uuid/composer.json
index 0a4681e0eeba..0e021a23c446 100644
--- a/core/lib/Drupal/Component/Uuid/composer.json
+++ b/core/lib/Drupal/Component/Uuid/composer.json
@@ -1,19 +1,23 @@
 {
-  "name": "drupal/core-uuid",
-  "description": "UUID generation and validation.",
-  "type": "library",
-  "license": "GPL-2.0-or-later",
-  "support": {
-    "issues": "https://www.drupal.org/project/issues/drupal",
-    "irc": "irc://irc.freenode.net/drupal-contribute",
-    "source": "https://www.drupal.org/project/drupal/git-instructions"
-  },
-  "require": {
-    "php": ">=7.3.0"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Uuid\\": ""
+    "name": "drupal/core-uuid",
+    "description": "UUID generation and validation.",
+    "type": "library",
+    "license": "GPL-2.0-or-later",
+    "support": {
+        "issues": "https://www.drupal.org/project/issues/drupal",
+        "source": "https://www.drupal.org/project/drupal/git-instructions"
+    },
+    "require": {
+        "php": ">=8.1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Uuid\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/lib/Drupal/Component/Version/composer.json b/core/lib/Drupal/Component/Version/composer.json
index 02e2814dda42..d7785a3a49ab 100644
--- a/core/lib/Drupal/Component/Version/composer.json
+++ b/core/lib/Drupal/Component/Version/composer.json
@@ -1,15 +1,22 @@
 {
-  "name": "drupal/core-version",
-  "description": "Utility classes for process Drupal specific version information.",
-  "keywords": ["drupal"],
-  "homepage": "https://www.drupal.org/project/drupal",
-  "license": "GPL-2.0-or-later",
-  "require": {
-    "php": ">=7.3.0"
-  },
-  "autoload": {
-    "psr-4": {
-      "Drupal\\Component\\Version\\": ""
+    "name": "drupal/core-version",
+    "description": "Utility classes for process Drupal specific version information.",
+    "keywords": [
+        "drupal"
+    ],
+    "homepage": "https://www.drupal.org/project/drupal",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=8.1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Drupal\\Component\\Version\\": ""
+        }
+    },
+    "extra": {
+        "_readme": [
+            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
+        ]
     }
-  }
 }
diff --git a/core/tests/Drupal/BuildTests/Composer/Component/ComponentsIsolatedBuildTest.php b/core/tests/Drupal/BuildTests/Composer/Component/ComponentsIsolatedBuildTest.php
new file mode 100644
index 000000000000..fdad24db7d65
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Composer/Component/ComponentsIsolatedBuildTest.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\BuildTests\Composer\Component;
+
+use Drupal\BuildTests\Composer\ComposerBuildTestBase;
+use Drupal\Composer\Composer;
+
+/**
+ * Try to install dependencies per component, using Composer.
+ *
+ * @group #slow
+ * @group Composer
+ * @group Component
+ *
+ * @coversNothing
+ *
+ * @requires externalCommand composer
+ */
+class ComponentsIsolatedBuildTest extends ComposerBuildTestBase {
+
+  /**
+   * Provides an array with relative paths to the component paths.
+   *
+   * @return array
+   *   An array with relative paths to the component paths.
+   */
+  public function provideComponentPaths(): array {
+    $data = [];
+    // During the dataProvider phase, there is not a workspace directory yet.
+    // So we will find relative paths and assemble them with the workspace
+    // path later.
+    $drupal_root = $this->getDrupalRoot();
+    $composer_json_finder = $this->getComponentPathsFinder($drupal_root);
+
+    /** @var \Symfony\Component\Finder\SplFileInfo $path */
+    foreach ($composer_json_finder->getIterator() as $path) {
+      $data[] = ['/' . $path->getRelativePath()];
+    }
+    return $data;
+  }
+
+  /**
+   * Test whether components' composer.json can be installed in isolation.
+   *
+   * @dataProvider provideComponentPaths
+   */
+  public function testComponentComposerJson(string $component_path): void {
+    // Only copy the components. Copy all of them because some of them depend on
+    // each other.
+    $finder = $this->getCodebaseFinder();
+    $finder->in($this->getDrupalRoot() . static::$componentsPath);
+    $this->copyCodebase($finder->getIterator());
+
+    $working_dir = $this->getWorkingPath() . static::$componentsPath . $component_path;
+
+    // We add path repositories so we can wire internal dependencies together.
+    $this->addExpectedRepositories($working_dir);
+
+    // Perform the installation.
+    $this->executeCommand("composer install --working-dir=$working_dir --no-interaction --no-progress");
+    $this->assertCommandSuccessful();
+  }
+
+  /**
+   * Adds expected repositories as path repositories to package under test.
+   *
+   * @param string $working_dir
+   *   The working directory.
+   */
+  protected function addExpectedRepositories(string $working_dir): void {
+    $repo_paths = [
+      'Render' => 'drupal/core-render',
+      'Utility' => 'drupal/core-utility',
+    ];
+    foreach ($repo_paths as $path => $package_name) {
+      $path_repo = $this->getWorkingPath() . static::$componentsPath . '/' . $path;
+      $repo_name = strtolower($path);
+      // Add path repositories with the current version number to the current
+      // package under test.
+      $drupal_version = Composer::drupalVersionBranch();
+      $this->executeCommand("composer config repositories.$repo_name " .
+        "'{\"type\": \"path\",\"url\": \"$path_repo\",\"options\": {\"versions\": {\"$package_name\": \"$drupal_version\"}}}' --working-dir=$working_dir");
+    }
+  }
+
+}
diff --git a/core/tests/Drupal/BuildTests/Composer/Component/ComponentsTaggedReleaseTest.php b/core/tests/Drupal/BuildTests/Composer/Component/ComponentsTaggedReleaseTest.php
new file mode 100644
index 000000000000..946a57d20ec7
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Composer/Component/ComponentsTaggedReleaseTest.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Drupal\BuildTests\Composer\Component;
+
+use Drupal\BuildTests\Composer\ComposerBuildTestBase;
+use Drupal\Composer\Composer;
+
+/**
+ * Demonstrate that the Component generator responds to release tagging.
+ *
+ * @group #slow
+ * @group Composer
+ * @group Component
+ *
+ * @coversNothing
+ *
+ * @requires externalCommand composer
+ */
+class ComponentsTaggedReleaseTest extends ComposerBuildTestBase {
+
+  /**
+   * Highly arbitrary version and constraint expectations.
+   *
+   * @return array
+   *   - First element is the tag that should be applied to \Drupal::version.
+   *   - Second element is the resulting constraint which should be present in
+   *     the component core dependencies.
+   */
+  public function providerVersionConstraint(): array {
+    return [
+      // [Tag, constraint]
+      '1.0.x-dev' => ['1.0.x-dev', '1.0.x-dev'],
+      '1.0.0-beta1' => ['1.0.0-beta1', '1.0.0-beta1'],
+      '1.0.0-rc1' => ['1.0.0-rc1', '1.0.0-rc1'],
+      '1.0.0' => ['1.0.0', '^1.0'],
+    ];
+  }
+
+  /**
+   * Validate release tagging and regeneration of dependencies.
+   *
+   * @dataProvider providerVersionConstraint
+   */
+  public function testReleaseTagging(string $tag, string $constraint): void {
+    $this->copyCodebase();
+    $drupal_root = $this->getWorkspaceDirectory();
+
+    // Set the core version.
+    Composer::setDrupalVersion($drupal_root, $tag);
+    $this->assertDrupalVersion($tag, $drupal_root);
+
+    // Emulate the release script.
+    // @see https://github.com/xjm/drupal_core_release/blob/main/tag.sh
+    $this->executeCommand("COMPOSER_ROOT_VERSION=\"$tag\" composer update drupal/core*");
+    $this->assertCommandSuccessful();
+    $this->assertErrorOutputContains('generateComponentPackages');
+
+    // Find all the components.
+    $component_finder = $this->getComponentPathsFinder($drupal_root);
+
+    // Loop through all the component packages.
+    /** @var \Symfony\Component\Finder\SplFileInfo $composer_json */
+    foreach ($component_finder->getIterator() as $composer_json) {
+      $composer_json_data = json_decode(file_get_contents($composer_json->getPathname()), TRUE);
+      $requires = array_merge(
+        $composer_json_data['require'] ?? [],
+        $composer_json_data['require-dev'] ?? []
+      );
+      // Required packages from drupal/core-* should have our constraint.
+      foreach ($requires as $package => $req_constraint) {
+        if (strpos($package, 'drupal/core-') !== FALSE) {
+          $this->assertEquals($constraint, $req_constraint);
+        }
+      }
+    }
+  }
+
+}
diff --git a/core/tests/Drupal/BuildTests/Composer/ComposerBuildTestBase.php b/core/tests/Drupal/BuildTests/Composer/ComposerBuildTestBase.php
new file mode 100644
index 000000000000..619fd8d10484
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Composer/ComposerBuildTestBase.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\BuildTests\Composer;
+
+use Drupal\BuildTests\Framework\BuildTestBase;
+use Symfony\Component\Finder\Finder;
+
+/**
+ * Base class for Composer build tests.
+ *
+ * @coversNothing
+ */
+abstract class ComposerBuildTestBase extends BuildTestBase {
+
+  /**
+   * Relative path from Drupal root to the Components directory.
+   *
+   * @var string
+   */
+  protected static $componentsPath = '/core/lib/Drupal/Component';
+
+  /**
+   * Assert that the VERSION constant in Drupal.php is the expected value.
+   *
+   * @param string $expectedVersion
+   *   The expected version.
+   * @param string $dir
+   *   The path to the site root.
+   *
+   * @internal
+   */
+  protected function assertDrupalVersion(string $expectedVersion, string $dir): void {
+    $drupal_php_path = $dir . '/core/lib/Drupal.php';
+    $this->assertFileExists($drupal_php_path);
+
+    // Read back the Drupal version that was set and assert it matches
+    // expectations
+    $this->executeCommand("php -r 'include \"$drupal_php_path\"; print \Drupal::VERSION;'");
+    $this->assertCommandSuccessful();
+    $this->assertCommandOutputContains($expectedVersion);
+  }
+
+  /**
+   * Find all the composer.json files for components.
+   *
+   * @param string $drupal_root
+   *   The Drupal root directory.
+   *
+   * @return \Symfony\Component\Finder\Finder
+   *   A Finder object with all the composer.json files for components.
+   */
+  protected function getComponentPathsFinder(string $drupal_root): Finder {
+    $finder = new Finder();
+    $finder->name('composer.json')
+      ->in($drupal_root . static::$componentsPath)
+      ->ignoreUnreadableDirs()
+      ->depth(1);
+
+    return $finder;
+  }
+
+}
diff --git a/core/tests/Drupal/BuildTests/Composer/Template/ComposerProjectTemplatesTest.php b/core/tests/Drupal/BuildTests/Composer/Template/ComposerProjectTemplatesTest.php
index 32c3e05de4f5..413b4d25cdf0 100644
--- a/core/tests/Drupal/BuildTests/Composer/Template/ComposerProjectTemplatesTest.php
+++ b/core/tests/Drupal/BuildTests/Composer/Template/ComposerProjectTemplatesTest.php
@@ -4,7 +4,7 @@
 
 use Composer\Json\JsonFile;
 use Composer\Semver\VersionParser;
-use Drupal\BuildTests\Framework\BuildTestBase;
+use Drupal\BuildTests\Composer\ComposerBuildTestBase;
 use Drupal\Composer\Composer;
 
 /**
@@ -25,7 +25,7 @@
  *
  * @requires externalCommand composer
  */
-class ComposerProjectTemplatesTest extends BuildTestBase {
+class ComposerProjectTemplatesTest extends ComposerBuildTestBase {
 
   /**
    * The minimum stability requirement for dependencies.
@@ -297,26 +297,6 @@ public function testTemplateCreateProject($project, $package_dir, $docroot_dir)
     }
   }
 
-  /**
-   * Assert that the VERSION constant in Drupal.php is the expected value.
-   *
-   * @param string $expectedVersion
-   *   The expected version.
-   * @param string $dir
-   *   The path to the site root.
-   *
-   * @internal
-   */
-  protected function assertDrupalVersion(string $expectedVersion, string $dir): void {
-    $drupal_php_path = $dir . '/core/lib/Drupal.php';
-    $this->assertFileExists($drupal_php_path);
-
-    // Read back the Drupal version that was set and assert it matches expectations.
-    $this->executeCommand("php -r 'include \"$drupal_php_path\"; print \Drupal::VERSION;'");
-    $this->assertCommandSuccessful();
-    $this->assertCommandOutputContains($expectedVersion);
-  }
-
   /**
    * Creates a test package that points to the templates.
    *
-- 
GitLab