247 lines
8.8 KiB
PHP
247 lines
8.8 KiB
PHP
<?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\Flex;
|
|
|
|
use Composer\Json\JsonFile;
|
|
use Composer\Json\JsonManipulator;
|
|
use Composer\Semver\Constraint\ConstraintInterface;
|
|
use Composer\Semver\Intervals;
|
|
use Composer\Semver\VersionParser;
|
|
|
|
/**
|
|
* Synchronize package.json files detected in installed PHP packages with
|
|
* the current application.
|
|
*/
|
|
class PackageJsonSynchronizer
|
|
{
|
|
private $rootDir;
|
|
private $vendorDir;
|
|
|
|
public function __construct(string $rootDir, string $vendorDir = 'vendor')
|
|
{
|
|
$this->rootDir = $rootDir;
|
|
$this->vendorDir = $vendorDir;
|
|
}
|
|
|
|
public function shouldSynchronize(): bool
|
|
{
|
|
return $this->rootDir && file_exists($this->rootDir.'/package.json');
|
|
}
|
|
|
|
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 ($phpPackages as $k => $phpPackage) {
|
|
if (\is_string($phpPackage)) {
|
|
// support for smooth upgrades from older flex versions
|
|
$phpPackages[$k] = $phpPackage = [
|
|
'name' => $phpPackage,
|
|
'keywords' => ['symfony-ux'],
|
|
];
|
|
}
|
|
$didAddLink = $this->addPackageJsonLink($phpPackage) || $didAddLink;
|
|
}
|
|
|
|
$this->registerPeerDependencies($phpPackages);
|
|
|
|
// Register controllers and entrypoints in controllers.json
|
|
$this->registerWebpackResources($phpPackages);
|
|
|
|
return $didAddLink;
|
|
}
|
|
|
|
private function removePackageJsonLinks(array $packageJson): bool
|
|
{
|
|
$didRemoveLink = false;
|
|
$jsDependencies = $packageJson['dependencies'] ?? [];
|
|
$jsDevDependencies = $packageJson['devDependencies'] ?? [];
|
|
|
|
foreach (['dependencies' => $jsDependencies, 'devDependencies' => $jsDevDependencies] as $key => $packages) {
|
|
foreach ($packages as $name => $version) {
|
|
if ('@' !== $name[0] || 0 !== strpos($version, 'file:')) {
|
|
continue;
|
|
}
|
|
|
|
$manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json'));
|
|
$manipulator->removeSubNode('devDependencies', $name);
|
|
file_put_contents($this->rootDir.'/package.json', $manipulator->getContents());
|
|
$didRemoveLink = true;
|
|
}
|
|
}
|
|
|
|
return $didRemoveLink;
|
|
}
|
|
|
|
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['name'], 'file:'.substr($packageJson->getPath(), 1 + \strlen($this->rootDir), -13));
|
|
|
|
$content = json_decode($manipulator->getContents(), true);
|
|
|
|
$devDependencies = $content['devDependencies'];
|
|
uksort($devDependencies, 'strnatcmp');
|
|
$manipulator->addMainKey('devDependencies', $devDependencies);
|
|
|
|
file_put_contents($this->rootDir.'/package.json', $manipulator->getContents());
|
|
|
|
return true;
|
|
}
|
|
|
|
private function registerWebpackResources(array $phpPackages)
|
|
{
|
|
if (!file_exists($controllersJsonPath = $this->rootDir.'/assets/controllers.json')) {
|
|
return;
|
|
}
|
|
|
|
$previousControllersJson = (new JsonFile($controllersJsonPath))->read();
|
|
$newControllersJson = [
|
|
'controllers' => [],
|
|
'entrypoints' => $previousControllersJson['entrypoints'],
|
|
];
|
|
|
|
foreach ($phpPackages as $phpPackage) {
|
|
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'][$name][$controllerName])) {
|
|
$config = [];
|
|
$config['enabled'] = $defaultConfig['enabled'];
|
|
$config['fetch'] = $defaultConfig['fetch'] ?? 'eager';
|
|
|
|
if (isset($defaultConfig['autoimport'])) {
|
|
$config['autoimport'] = $defaultConfig['autoimport'];
|
|
}
|
|
|
|
$newControllersJson['controllers'][$name][$controllerName] = $config;
|
|
|
|
continue;
|
|
}
|
|
|
|
// Otherwise, the package exists: merge new config with uer config
|
|
$previousConfig = $previousControllersJson['controllers'][$name][$controllerName];
|
|
|
|
$config = [];
|
|
$config['enabled'] = $previousConfig['enabled'];
|
|
$config['fetch'] = $previousConfig['fetch'] ?? 'eager';
|
|
|
|
if (isset($defaultConfig['autoimport'])) {
|
|
$config['autoimport'] = [];
|
|
|
|
// Use for each autoimport either the previous config if one existed or the default config otherwise
|
|
foreach ($defaultConfig['autoimport'] as $autoimport => $enabled) {
|
|
$config['autoimport'][$autoimport] = $previousConfig['autoimport'][$autoimport] ?? $enabled;
|
|
}
|
|
}
|
|
|
|
$newControllersJson['controllers'][$name][$controllerName] = $config;
|
|
}
|
|
|
|
foreach ($packageJson->read()['symfony']['entrypoints'] ?? [] as $entrypoint => $filename) {
|
|
if (!isset($newControllersJson['entrypoints'][$entrypoint])) {
|
|
$newControllersJson['entrypoints'][$entrypoint] = $filename;
|
|
}
|
|
}
|
|
}
|
|
|
|
file_put_contents($controllersJsonPath, json_encode($newControllersJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)."\n");
|
|
}
|
|
|
|
public function registerPeerDependencies(array $phpPackages)
|
|
{
|
|
$peerDependencies = [];
|
|
|
|
foreach ($phpPackages as $phpPackage) {
|
|
if (!$packageJson = $this->resolvePackageJson($phpPackage)) {
|
|
continue;
|
|
}
|
|
|
|
$versionParser = new VersionParser();
|
|
|
|
foreach ($packageJson->read()['peerDependencies'] ?? [] as $peerDependency => $constraint) {
|
|
$peerDependencies[$peerDependency][$constraint] = $versionParser->parseConstraints($constraint);
|
|
}
|
|
}
|
|
|
|
if (!$peerDependencies) {
|
|
return;
|
|
}
|
|
|
|
$manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json'));
|
|
$content = json_decode($manipulator->getContents(), true);
|
|
$devDependencies = $content['devDependencies'] ?? [];
|
|
|
|
foreach ($peerDependencies as $peerDependency => $constraints) {
|
|
$devDependencies[$peerDependency] = $this->compactConstraints($constraints);
|
|
}
|
|
uksort($devDependencies, 'strnatcmp');
|
|
$manipulator->addMainKey('devDependencies', $devDependencies);
|
|
|
|
file_put_contents($this->rootDir.'/package.json', $manipulator->getContents());
|
|
}
|
|
|
|
private function resolvePackageJson(array $phpPackage): ?JsonFile
|
|
{
|
|
$packageDir = $this->rootDir.'/'.$this->vendorDir.'/'.$phpPackage['name'];
|
|
|
|
if (!\in_array('symfony-ux', $phpPackage['keywords'] ?? [], true)) {
|
|
return null;
|
|
}
|
|
|
|
foreach (['/assets', '/Resources/assets'] as $subdir) {
|
|
$packageJsonPath = $packageDir.$subdir.'/package.json';
|
|
|
|
if (!file_exists($packageJsonPath)) {
|
|
continue;
|
|
}
|
|
|
|
return new JsonFile($packageJsonPath);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param ConstraintInterface[] $constraints
|
|
*/
|
|
private function compactConstraints(array $constraints): string
|
|
{
|
|
if (method_exists(Intervals::class, 'isSubsetOf')) {
|
|
foreach ($constraints as $k1 => $constraint1) {
|
|
foreach ($constraints as $k2 => $constraint2) {
|
|
if ($k1 !== $k2 && Intervals::isSubsetOf($constraint1, $constraint2)) {
|
|
unset($constraints[$k2]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uksort($constraints, 'strnatcmp');
|
|
|
|
foreach ($constraints as $k => $constraint) {
|
|
$constraints[$k] = \count($constraints) > 1 && false !== strpos($k, '|') ? '('.$k.')' : $k;
|
|
}
|
|
|
|
return implode(',', $constraints);
|
|
}
|
|
}
|