Skip to content

Commit 92d9551

Browse files
morawskimnicolas-grekas
authored andcommitted
Real composer scripts
1 parent 62d5c38 commit 92d9551

File tree

3 files changed

+357
-0
lines changed

3 files changed

+357
-0
lines changed

src/Configurator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public function __construct(Composer $composer, IOInterface $io, Options $option
4343
'container' => Configurator\ContainerConfigurator::class,
4444
'makefile' => Configurator\MakefileConfigurator::class,
4545
'composer-scripts' => Configurator\ComposerScriptsConfigurator::class,
46+
'composer-commands' => Configurator\ComposerCommandsConfigurator::class,
4647
'gitignore' => Configurator\GitignoreConfigurator::class,
4748
'dockerfile' => Configurator\DockerfileConfigurator::class,
4849
'docker-compose' => Configurator\DockerComposeConfigurator::class,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Flex\Configurator;
13+
14+
use Composer\Factory;
15+
use Composer\Json\JsonFile;
16+
use Composer\Json\JsonManipulator;
17+
use Symfony\Flex\Lock;
18+
use Symfony\Flex\Recipe;
19+
use Symfony\Flex\Update\RecipeUpdate;
20+
21+
/**
22+
* @author Marcin Morawski <marcin@morawskim.pl>
23+
*/
24+
class ComposerCommandsConfigurator extends AbstractConfigurator
25+
{
26+
public function configure(Recipe $recipe, $scripts, Lock $lock, array $options = [])
27+
{
28+
$json = new JsonFile(Factory::getComposerFile());
29+
30+
file_put_contents($json->getPath(), $this->configureScripts($scripts, $json));
31+
}
32+
33+
public function unconfigure(Recipe $recipe, $scripts, Lock $lock)
34+
{
35+
$json = new JsonFile(Factory::getComposerFile());
36+
37+
$manipulator = new JsonManipulator(file_get_contents($json->getPath()));
38+
foreach ($scripts as $key => $command) {
39+
$manipulator->removeSubNode('scripts', $key);
40+
}
41+
42+
file_put_contents($json->getPath(), $manipulator->getContents());
43+
}
44+
45+
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
46+
{
47+
$json = new JsonFile(Factory::getComposerFile());
48+
$jsonPath = ltrim(str_replace($recipeUpdate->getRootDir(), '', $json->getPath()), '/\\');
49+
50+
$recipeUpdate->setOriginalFile(
51+
$jsonPath,
52+
$this->configureScripts($originalConfig, $json)
53+
);
54+
$recipeUpdate->setNewFile(
55+
$jsonPath,
56+
$this->configureScripts($newConfig, $json)
57+
);
58+
}
59+
60+
private function configureScripts(array $scripts, JsonFile $json): string
61+
{
62+
$manipulator = new JsonManipulator(file_get_contents($json->getPath()));
63+
foreach ($scripts as $cmdName => $script) {
64+
$manipulator->addSubNode('scripts', $cmdName, $script);
65+
}
66+
67+
return $manipulator->getContents();
68+
}
69+
}
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Flex\Tests\Configurator;
13+
14+
use Composer\Composer;
15+
use Composer\IO\IOInterface;
16+
use Composer\Util\Platform;
17+
use PHPUnit\Framework\TestCase;
18+
use Symfony\Flex\Configurator\ComposerCommandsConfigurator;
19+
use Symfony\Flex\Lock;
20+
use Symfony\Flex\Options;
21+
use Symfony\Flex\Recipe;
22+
use Symfony\Flex\Update\RecipeUpdate;
23+
24+
class ComposerCommandConfiguratorTest extends TestCase
25+
{
26+
protected function setUp(): void
27+
{
28+
@mkdir(FLEX_TEST_DIR);
29+
if (method_exists(Platform::class, 'putEnv')) {
30+
Platform::putEnv('COMPOSER', FLEX_TEST_DIR.'/composer.json');
31+
} else {
32+
putenv('COMPOSER='.FLEX_TEST_DIR.'/composer.json');
33+
}
34+
}
35+
36+
protected function tearDown(): void
37+
{
38+
@unlink(FLEX_TEST_DIR.'/composer.json');
39+
@rmdir(FLEX_TEST_DIR);
40+
if (method_exists(Platform::class, 'clearEnv')) {
41+
Platform::clearEnv('COMPOSER');
42+
} else {
43+
putenv('COMPOSER');
44+
}
45+
}
46+
47+
/**
48+
* @dataProvider providerForConfigureMethod
49+
*/
50+
public function testConfigure($composerSchema, string $expectedComposerJson): void
51+
{
52+
file_put_contents(FLEX_TEST_DIR.'/composer.json', json_encode($composerSchema, \JSON_PRETTY_PRINT));
53+
54+
$configurator = new ComposerCommandsConfigurator(
55+
$this->createMock(Composer::class),
56+
$this->createMock(IOInterface::class),
57+
new Options(['root-dir' => FLEX_TEST_DIR])
58+
);
59+
60+
$recipe = $this->getMockBuilder(Recipe::class)->disableOriginalConstructor()->getMock();
61+
$lock = $this->getMockBuilder(Lock::class)->disableOriginalConstructor()->getMock();
62+
63+
$configurator->configure($recipe, [
64+
'do:cool-stuff' => 'symfony-cmd',
65+
], $lock);
66+
$this->assertEquals(
67+
$expectedComposerJson,
68+
file_get_contents(FLEX_TEST_DIR.'/composer.json')
69+
);
70+
}
71+
72+
public static function providerForConfigureMethod(): iterable
73+
{
74+
yield 'without_scripts_block' => [
75+
new \stdClass(),
76+
<<<EOF
77+
{
78+
"scripts": {
79+
"do:cool-stuff": "symfony-cmd"
80+
}
81+
}
82+
83+
EOF,
84+
];
85+
86+
yield 'with_existing_command' => [
87+
[
88+
'scripts' => [
89+
'foo' => 'bar',
90+
],
91+
],
92+
<<<EOF
93+
{
94+
"scripts": {
95+
"foo": "bar",
96+
"do:cool-stuff": "symfony-cmd"
97+
}
98+
}
99+
100+
EOF,
101+
];
102+
103+
yield 'with_existing_auto_scripts' => [
104+
[
105+
'scripts' => [
106+
'auto-scripts' => [
107+
'cache:clear' => 'symfony-cmd',
108+
'assets:install %PUBLIC_DIR%' => 'symfony-cmd',
109+
],
110+
'post-install-cmd' => ['@auto-scripts'],
111+
'post-update-cmd' => ['@auto-scripts'],
112+
],
113+
],
114+
<<<EOF
115+
{
116+
"scripts": {
117+
"auto-scripts": {
118+
"cache:clear": "symfony-cmd",
119+
"assets:install %PUBLIC_DIR%": "symfony-cmd"
120+
},
121+
"post-install-cmd": [
122+
"@auto-scripts"
123+
],
124+
"post-update-cmd": [
125+
"@auto-scripts"
126+
],
127+
"do:cool-stuff": "symfony-cmd"
128+
}
129+
}
130+
131+
EOF,
132+
];
133+
}
134+
135+
/**
136+
* @dataProvider providerForUnconfigureMethod
137+
*/
138+
public function testUnconfigure($composerSchema, string $expectedComposerJson): void
139+
{
140+
file_put_contents(FLEX_TEST_DIR.'/composer.json', json_encode($composerSchema, \JSON_PRETTY_PRINT));
141+
142+
$configurator = new ComposerCommandsConfigurator(
143+
$this->createMock(Composer::class),
144+
$this->createMock(IOInterface::class),
145+
new Options(['root-dir' => FLEX_TEST_DIR])
146+
);
147+
148+
$recipe = $this->createMock(Recipe::class);
149+
$lock = $this->createMock(Lock::class);
150+
151+
$configurator->unconfigure($recipe, [
152+
'do:cool-stuff' => 'symfony-cmd',
153+
], $lock);
154+
$this->assertEquals(
155+
$expectedComposerJson,
156+
file_get_contents(FLEX_TEST_DIR.'/composer.json')
157+
);
158+
}
159+
160+
public static function providerForUnconfigureMethod(): iterable
161+
{
162+
yield 'unconfigure_one_command_with_auto_scripts' => [
163+
[
164+
'scripts' => [
165+
'auto-scripts' => [
166+
'cache:clear' => 'symfony-cmd',
167+
'assets:install %PUBLIC_DIR%' => 'symfony-cmd',
168+
],
169+
'post-install-cmd' => ['@auto-scripts'],
170+
'post-update-cmd' => ['@auto-scripts'],
171+
'do:cool-stuff' => 'symfony-cmd',
172+
'do:another-cool-stuff' => 'symfony-cmd-2',
173+
],
174+
],
175+
<<<EOF
176+
{
177+
"scripts": {
178+
"auto-scripts": {
179+
"cache:clear": "symfony-cmd",
180+
"assets:install %PUBLIC_DIR%": "symfony-cmd"
181+
},
182+
"post-install-cmd": [
183+
"@auto-scripts"
184+
],
185+
"post-update-cmd": [
186+
"@auto-scripts"
187+
],
188+
"do:another-cool-stuff": "symfony-cmd-2"
189+
}
190+
}
191+
192+
EOF,
193+
];
194+
195+
yield 'unconfigure_command' => [
196+
[
197+
'scripts' => [
198+
'do:another-cool-stuff' => 'symfony-cmd-2',
199+
'do:cool-stuff' => 'symfony-cmd',
200+
],
201+
],
202+
<<<EOF
203+
{
204+
"scripts": {
205+
"do:another-cool-stuff": "symfony-cmd-2"
206+
}
207+
}
208+
209+
EOF,
210+
];
211+
}
212+
213+
public function testUpdate(): void
214+
{
215+
$configurator = new ComposerCommandsConfigurator(
216+
$this->createMock(Composer::class),
217+
$this->createMock(IOInterface::class),
218+
new Options(['root-dir' => FLEX_TEST_DIR])
219+
);
220+
221+
$recipeUpdate = new RecipeUpdate(
222+
$this->createMock(Recipe::class),
223+
$this->createMock(Recipe::class),
224+
$this->createMock(Lock::class),
225+
FLEX_TEST_DIR
226+
);
227+
228+
file_put_contents(FLEX_TEST_DIR.'/composer.json', json_encode([
229+
'scripts' => [
230+
'auto-scripts' => [
231+
'cache:clear' => 'symfony-cmd',
232+
'assets:install %PUBLIC_DIR%' => 'symfony-cmd',
233+
],
234+
'post-install-cmd' => ['@auto-scripts'],
235+
'post-update-cmd' => ['@auto-scripts'],
236+
'foo' => 'bar',
237+
],
238+
], \JSON_PRETTY_PRINT));
239+
240+
$configurator->update(
241+
$recipeUpdate,
242+
['foo' => 'bar'],
243+
['foo' => 'baz', 'do:cool-stuff' => 'symfony-cmd']
244+
);
245+
246+
$expectedComposerJsonOriginal = <<<EOF
247+
{
248+
"scripts": {
249+
"auto-scripts": {
250+
"cache:clear": "symfony-cmd",
251+
"assets:install %PUBLIC_DIR%": "symfony-cmd"
252+
},
253+
"post-install-cmd": [
254+
"@auto-scripts"
255+
],
256+
"post-update-cmd": [
257+
"@auto-scripts"
258+
],
259+
"foo": "bar"
260+
}
261+
}
262+
263+
EOF;
264+
$this->assertSame(['composer.json' => $expectedComposerJsonOriginal], $recipeUpdate->getOriginalFiles());
265+
266+
$expectedComposerJsonNew = <<<EOF
267+
{
268+
"scripts": {
269+
"auto-scripts": {
270+
"cache:clear": "symfony-cmd",
271+
"assets:install %PUBLIC_DIR%": "symfony-cmd"
272+
},
273+
"post-install-cmd": [
274+
"@auto-scripts"
275+
],
276+
"post-update-cmd": [
277+
"@auto-scripts"
278+
],
279+
"foo": "baz",
280+
"do:cool-stuff": "symfony-cmd"
281+
}
282+
}
283+
284+
EOF;
285+
$this->assertSame(['composer.json' => $expectedComposerJsonNew], $recipeUpdate->getNewFiles());
286+
}
287+
}

0 commit comments

Comments
 (0)