Skip to content

Commit e0d37f9

Browse files
Merge branch '1.x' into 2.x
* 1.x: Fix tests Refactor PackageJsonSynchronizer to prevent unintentional duplicate dependencies Skipping missing source directories during copy-from-package unconfiguration Fixing missing blank line at end of modified patch files
2 parents 82891fd + 7e2319a commit e0d37f9

File tree

8 files changed

+141
-88
lines changed

8 files changed

+141
-88
lines changed

phpunit.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<php>
2020
<ini name="error_reporting" value="-1" />
2121
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
22+
<env name="LC_ALL" value="C" />
2223
</php>
2324

2425
<filter>

src/Configurator/CopyFromPackageConfigurator.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ public function copyFile(string $source, string $target, array $options)
145145

146146
private function removeFilesFromDir(string $source, string $target)
147147
{
148+
if (!is_dir($source)) {
149+
return;
150+
}
148151
$iterator = $this->createSourceIterator($source, \RecursiveIteratorIterator::CHILD_FIRST);
149152
foreach ($iterator as $item) {
150153
$targetPath = $this->path->concatenate([$target, $iterator->getSubPathName()]);

src/PackageJsonSynchronizer.php

Lines changed: 76 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@
1313

1414
use Composer\Json\JsonFile;
1515
use Composer\Json\JsonManipulator;
16-
use Composer\Semver\Constraint\ConstraintInterface;
17-
use Composer\Semver\Intervals;
18-
use Composer\Semver\VersionParser;
1916
use Seld\JsonLint\ParsingException;
2017

2118
/**
@@ -40,14 +37,17 @@ public function shouldSynchronize(): bool
4037

4138
public function synchronize(array $phpPackages): bool
4239
{
43-
// Remove all links and add again only the existing packages
4440
try {
45-
$didAddLink = $this->removePackageJsonLinks((new JsonFile($this->rootDir.'/package.json'))->read());
41+
JsonFile::parseJson(file_get_contents($this->rootDir.'/package.json'));
4642
} catch (ParsingException $e) {
4743
// if package.json is invalid (possible during a recipe upgrade), we can't update the file
4844
return false;
4945
}
5046

47+
$didChangePackageJson = $this->removeObsoletePackageJsonLinks();
48+
49+
$dependencies = [];
50+
5151
foreach ($phpPackages as $k => $phpPackage) {
5252
if (\is_string($phpPackage)) {
5353
// support for smooth upgrades from older flex versions
@@ -56,22 +56,29 @@ public function synchronize(array $phpPackages): bool
5656
'keywords' => ['symfony-ux'],
5757
];
5858
}
59-
$didAddLink = $this->addPackageJsonLink($phpPackage) || $didAddLink;
59+
60+
foreach ($this->resolvePackageDependencies($phpPackage) as $dependency => $constraint) {
61+
$dependencies[$dependency][$phpPackage['name']] = $constraint;
62+
}
6063
}
6164

62-
$this->registerPeerDependencies($phpPackages);
65+
$didChangePackageJson = $this->registerDependencies($dependencies) || $didChangePackageJson;
6366

6467
// Register controllers and entrypoints in controllers.json
6568
$this->registerWebpackResources($phpPackages);
6669

67-
return $didAddLink;
70+
return $didChangePackageJson;
6871
}
6972

70-
private function removePackageJsonLinks(array $packageJson): bool
73+
private function removeObsoletePackageJsonLinks(): bool
7174
{
72-
$didRemoveLink = false;
73-
$jsDependencies = $packageJson['dependencies'] ?? [];
74-
$jsDevDependencies = $packageJson['devDependencies'] ?? [];
75+
$didChangePackageJson = false;
76+
77+
$manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json'));
78+
$content = json_decode($manipulator->getContents(), true);
79+
80+
$jsDependencies = $content['dependencies'] ?? [];
81+
$jsDevDependencies = $content['devDependencies'] ?? [];
7582

7683
foreach (['dependencies' => $jsDependencies, 'devDependencies' => $jsDevDependencies] as $key => $packages) {
7784
foreach ($packages as $name => $version) {
@@ -82,39 +89,78 @@ private function removePackageJsonLinks(array $packageJson): bool
8289
continue;
8390
}
8491

85-
$manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json'));
86-
$manipulator->removeSubNode('devDependencies', $name);
87-
file_put_contents($this->rootDir.'/package.json', $manipulator->getContents());
88-
$didRemoveLink = true;
92+
$manipulator->removeSubNode($key, $name);
93+
$didChangePackageJson = true;
8994
}
9095
}
9196

92-
return $didRemoveLink;
97+
file_put_contents($this->rootDir.'/package.json', $manipulator->getContents());
98+
99+
return $didChangePackageJson;
93100
}
94101

95-
private function addPackageJsonLink(array $phpPackage): bool
102+
private function resolvePackageDependencies($phpPackage): array
96103
{
104+
$dependencies = [];
105+
97106
if (!$packageJson = $this->resolvePackageJson($phpPackage)) {
98-
return false;
107+
return $dependencies;
99108
}
100109

101-
$manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json'));
102-
$manipulator->addSubNode('devDependencies', '@'.$phpPackage['name'], 'file:'.substr($packageJson->getPath(), 1 + \strlen($this->rootDir), -13));
110+
$dependencies['@'.$phpPackage['name']] = 'file:'.substr($packageJson->getPath(), 1 + \strlen($this->rootDir), -13);
111+
112+
foreach ($packageJson->read()['peerDependencies'] ?? [] as $peerDependency => $constraint) {
113+
$dependencies[$peerDependency] = $constraint;
114+
}
115+
116+
return $dependencies;
117+
}
118+
119+
private function registerDependencies(array $flexDependencies): bool
120+
{
121+
$didChangePackageJson = false;
103122

123+
$manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json'));
104124
$content = json_decode($manipulator->getContents(), true);
105125

106-
$devDependencies = $content['devDependencies'];
107-
uksort($devDependencies, 'strnatcmp');
108-
$manipulator->addMainKey('devDependencies', $devDependencies);
126+
foreach ($flexDependencies as $dependency => $constraints) {
127+
if (1 !== \count($constraints) && 1 !== \count(array_count_values($constraints))) {
128+
// If the flex packages have a colliding peer dependency, leave the resolution to the user
129+
continue;
130+
}
109131

110-
$newContents = $manipulator->getContents();
111-
if ($newContents === file_get_contents($this->rootDir.'/package.json')) {
112-
return false;
132+
$constraint = array_shift($constraints);
133+
134+
$parentNode = isset($content['dependencies'][$dependency]) ? 'dependencies' : 'devDependencies';
135+
if (!isset($content[$parentNode][$dependency])) {
136+
$content['devDependencies'][$dependency] = $constraint;
137+
$didChangePackageJson = true;
138+
} elseif ($constraint !== $content[$parentNode][$dependency]) {
139+
$content[$parentNode][$dependency] = $constraint;
140+
$didChangePackageJson = true;
141+
}
113142
}
114143

115-
file_put_contents($this->rootDir.'/package.json', $newContents);
144+
if ($didChangePackageJson) {
145+
if (isset($content['dependencies'])) {
146+
$manipulator->addMainKey('dependencies', $content['dependencies']);
147+
}
148+
149+
if (isset($content['devDependencies'])) {
150+
$devDependencies = $content['devDependencies'];
151+
uksort($devDependencies, 'strnatcmp');
152+
$manipulator->addMainKey('devDependencies', $devDependencies);
153+
}
154+
155+
$newContents = $manipulator->getContents();
156+
if ($newContents === file_get_contents($this->rootDir.'/package.json')) {
157+
return false;
158+
}
159+
160+
file_put_contents($this->rootDir.'/package.json', $manipulator->getContents());
161+
}
116162

117-
return true;
163+
return $didChangePackageJson;
118164
}
119165

120166
private function registerWebpackResources(array $phpPackages)
@@ -151,7 +197,7 @@ private function registerWebpackResources(array $phpPackages)
151197
continue;
152198
}
153199

154-
// Otherwise, the package exists: merge new config with uer config
200+
// Otherwise, the package exists: merge new config with user config
155201
$previousConfig = $previousControllersJson['controllers'][$name][$controllerName];
156202

157203
$config = [];
@@ -180,39 +226,6 @@ private function registerWebpackResources(array $phpPackages)
180226
file_put_contents($controllersJsonPath, json_encode($newControllersJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)."\n");
181227
}
182228

183-
public function registerPeerDependencies(array $phpPackages)
184-
{
185-
$peerDependencies = [];
186-
187-
foreach ($phpPackages as $phpPackage) {
188-
if (!$packageJson = $this->resolvePackageJson($phpPackage)) {
189-
continue;
190-
}
191-
192-
$versionParser = new VersionParser();
193-
194-
foreach ($packageJson->read()['peerDependencies'] ?? [] as $peerDependency => $constraint) {
195-
$peerDependencies[$peerDependency][$constraint] = $versionParser->parseConstraints($constraint);
196-
}
197-
}
198-
199-
if (!$peerDependencies) {
200-
return;
201-
}
202-
203-
$manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json'));
204-
$content = json_decode($manipulator->getContents(), true);
205-
$devDependencies = $content['devDependencies'] ?? [];
206-
207-
foreach ($peerDependencies as $peerDependency => $constraints) {
208-
$devDependencies[$peerDependency] = $this->compactConstraints($constraints);
209-
}
210-
uksort($devDependencies, 'strnatcmp');
211-
$manipulator->addMainKey('devDependencies', $devDependencies);
212-
213-
file_put_contents($this->rootDir.'/package.json', $manipulator->getContents());
214-
}
215-
216229
private function resolvePackageJson(array $phpPackage): ?JsonFile
217230
{
218231
$packageDir = $this->rootDir.'/'.$this->vendorDir.'/'.$phpPackage['name'];
@@ -233,26 +246,4 @@ private function resolvePackageJson(array $phpPackage): ?JsonFile
233246

234247
return null;
235248
}
236-
237-
/**
238-
* @param ConstraintInterface[] $constraints
239-
*/
240-
private function compactConstraints(array $constraints): string
241-
{
242-
foreach ($constraints as $k1 => $constraint1) {
243-
foreach ($constraints as $k2 => $constraint2) {
244-
if ($k1 !== $k2 && Intervals::isSubsetOf($constraint1, $constraint2)) {
245-
unset($constraints[$k2]);
246-
}
247-
}
248-
}
249-
250-
uksort($constraints, 'strnatcmp');
251-
252-
foreach ($constraints as $k => $constraint) {
253-
$constraints[$k] = \count($constraints) > 1 && false !== strpos($k, '|') ? '('.$k.')' : $k;
254-
}
255-
256-
return implode(',', $constraints);
257-
}
258249
}

src/Update/DiffHelper.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ public static function removeFilesFromPatch(string $patch, array $files, array &
3535
$patch = $contentBefore.substr($patch, $end);
3636
}
3737

38+
// valid patches end with a blank line
39+
if ($patch && "\n" !== substr($patch, \strlen($patch) - 1, 1)) {
40+
$patch = $patch."\n";
41+
}
42+
3843
return $patch;
3944
}
4045
}

tests/Configurator/CopyFromPackageConfiguratorTest.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,11 @@ public function testUnconfigure()
110110
file_put_contents($this->targetFile, '');
111111
$this->assertFileExists($this->targetFile);
112112
$lock = $this->getMockBuilder(Lock::class)->disableOriginalConstructor()->getMock();
113-
$this->createConfigurator()->unconfigure($this->recipe, [$this->sourceFileRelativePath => $this->targetFileRelativePath], $lock);
113+
$this->createConfigurator()->unconfigure(
114+
$this->recipe,
115+
[$this->sourceFileRelativePath => $this->targetFileRelativePath, 'missingdir/' => ''],
116+
$lock
117+
);
114118
$this->assertFileDoesNotExist($this->targetFile);
115119
}
116120

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "symfony/fixture",
3+
"dependencies": {
4+
"@hotcookies": "^1.1|^2",
5+
"@hotdogs": "^2",
6+
"@symfony/existing-package": "file:vendor/symfony/existing-package/Resources/assets"
7+
},
8+
"devDependencies": {
9+
"@symfony/stimulus-bridge": "^1.0.0",
10+
"stimulus": "^1.1.1"
11+
},
12+
"browserslist": [
13+
"defaults"
14+
]
15+
}

tests/PackageJsonSynchronizerTest.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\Flex\Tests;
1313

14-
use Composer\Semver\Intervals;
1514
use PHPUnit\Framework\TestCase;
1615
use Symfony\Component\Filesystem\Filesystem;
1716
use Symfony\Flex\PackageJsonSynchronizer;
@@ -145,7 +144,6 @@ public function testSynchronizeNewPackage()
145144
'{
146145
"name": "symfony/fixture",
147146
"devDependencies": {
148-
"@hotcookies": "'.(method_exists(Intervals::class, 'isSubsetOf') ? '^1.1' : '^1.1,(^1.1|^2)').'",
149147
"@hotdogs": "^2",
150148
"@symfony/existing-package": "file:vendor/symfony/existing-package/Resources/assets",
151149
"@symfony/new-package": "file:vendor/symfony/new-package/assets",
@@ -215,4 +213,36 @@ public function testArrayFormattingHasNotChanged()
215213
trim(file_get_contents($this->tempDir.'/package.json'))
216214
);
217215
}
216+
217+
public function testExistingElevatedPackage()
218+
{
219+
(new Filesystem())->copy($this->tempDir.'/elevated_dependencies_package.json', $this->tempDir.'/package.json', true);
220+
221+
$this->synchronizer->synchronize([
222+
[
223+
'name' => 'symfony/existing-package',
224+
'keywords' => ['symfony-ux'],
225+
],
226+
]);
227+
228+
// Should keep existing package references and config
229+
$this->assertSame(
230+
[
231+
'name' => 'symfony/fixture',
232+
'dependencies' => [
233+
'@hotcookies' => '^1.1|^2',
234+
'@hotdogs' => '^2',
235+
'@symfony/existing-package' => 'file:vendor/symfony/existing-package/Resources/assets',
236+
],
237+
'devDependencies' => [
238+
'@symfony/stimulus-bridge' => '^1.0.0',
239+
'stimulus' => '^1.1.1',
240+
],
241+
'browserslist' => [
242+
'defaults',
243+
],
244+
],
245+
json_decode(file_get_contents($this->tempDir.'/package.json'), true)
246+
);
247+
}
218248
}

tests/Update/DiffHelperTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ public function getRemoveFilesFromPatchTests(): iterable
105105
+ dbal:
106106
+ # "TEST_TOKEN" is typically set by ParaTest
107107
+ dbname_suffix: '_test%env(default::TEST_TOKEN)%'
108+
108109
EOF
109110
, [
110111
'.env' => <<<EOF
@@ -154,6 +155,7 @@ public function getRemoveFilesFromPatchTests(): iterable
154155
+ dbal:
155156
+ # "TEST_TOKEN" is typically set by ParaTest
156157
+ dbname_suffix: '_test%env(default::TEST_TOKEN)%'
158+
157159
EOF
158160
, [
159161
'config/packages/doctrine.yaml' => <<<EOF
@@ -204,6 +206,7 @@ public function getRemoveFilesFromPatchTests(): iterable
204206
- type: pool
205207
- pool: doctrine.system_cache_pool
206208
query_cache_driver:
209+
207210
EOF
208211
, [
209212
'config/packages/test/doctrine.yaml' => <<<EOF
@@ -244,6 +247,7 @@ public function getRemoveFilesFromPatchTests(): iterable
244247
- type: pool
245248
- pool: doctrine.system_cache_pool
246249
query_cache_driver:
250+
247251
EOF
248252
, [
249253
'config/packages/test/doctrine.yaml' => <<<EOF

0 commit comments

Comments
 (0)