Skip to content

Commit 9e60da8

Browse files
committed
auto install required js packages
1 parent 12c8384 commit 9e60da8

File tree

5 files changed

+139
-23
lines changed

5 files changed

+139
-23
lines changed

src/JsPackageManager.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle 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\Bundle\MakerBundle;
13+
14+
use Symfony\Component\Process\ExecutableFinder;
15+
use Symfony\Component\Process\Process;
16+
17+
/**
18+
* @author Kevin Bond <kevinbond@gmail.com>
19+
*/
20+
final class JsPackageManager
21+
{
22+
private $executableFinder;
23+
private $files;
24+
25+
public function __construct(FileManager $fileManager)
26+
{
27+
$this->executableFinder = new ExecutableFinder();
28+
$this->files = $fileManager;
29+
}
30+
31+
public function add(string $package, string $version): void
32+
{
33+
$packageWithVersion = "{$package}@{$version}";
34+
35+
if ($yarn = $this->executableFinder->find('yarn')) {
36+
$command = [$yarn, 'add', $packageWithVersion, '--dev'];
37+
} elseif ($npm = $this->executableFinder->find('npm')) {
38+
$command = [$npm, 'install', $packageWithVersion, '--save-dev'];
39+
} else {
40+
$this->addToPackageJson($package, $version);
41+
42+
return;
43+
}
44+
45+
(new Process($command, $this->files->getRootDirectory()))->run();
46+
}
47+
48+
public function install(): void
49+
{
50+
(new Process([$this->bin(), 'install'], $this->files->getRootDirectory()))->run();
51+
}
52+
53+
public function run(string $script): void
54+
{
55+
(new Process([$this->bin(), 'run', $script], $this->files->getRootDirectory()))->run();
56+
}
57+
58+
public function isAvailable(): bool
59+
{
60+
try {
61+
$this->bin();
62+
63+
return true;
64+
} catch (\RuntimeException $e) {
65+
return false;
66+
}
67+
}
68+
69+
private function bin(): string
70+
{
71+
if (!$bin = $this->executableFinder->find('yarn') ?? $this->executableFinder->find('npm')) {
72+
throw new \RuntimeException('Unable to find js package manager.');
73+
}
74+
75+
return $bin;
76+
}
77+
78+
private function addToPackageJson(string $package, string $version): void
79+
{
80+
$packageJson = json_decode($this->files->getFileContents('package.json'), true);
81+
$devDeps = $packageJson['devDependencies'] ?? [];
82+
$devDeps[$package] = $version;
83+
84+
ksort($devDeps);
85+
86+
$packageJson['devDependencies'] = $devDeps;
87+
88+
$this->files->dumpFile('package.json', json_encode($packageJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
89+
}
90+
}

src/Maker/MakeScaffold.php

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
use Symfony\Bundle\MakerBundle\FileManager;
1818
use Symfony\Bundle\MakerBundle\Generator;
1919
use Symfony\Bundle\MakerBundle\InputConfiguration;
20+
use Symfony\Bundle\MakerBundle\JsPackageManager;
2021
use Symfony\Component\Console\Command\Command;
2122
use Symfony\Component\Console\Input\InputArgument;
2223
use Symfony\Component\Console\Input\InputInterface;
2324
use Symfony\Component\Filesystem\Filesystem;
2425
use Symfony\Component\Finder\Finder;
2526
use Symfony\Component\HttpKernel\Kernel;
27+
use Symfony\Component\Process\ExecutableFinder;
2628
use Symfony\Component\Process\Process;
2729

2830
/**
@@ -31,13 +33,17 @@
3133
final class MakeScaffold extends AbstractMaker
3234
{
3335
private $files;
36+
private $jsPackageManager;
3437
private $availableScaffolds;
38+
private $composerBin;
3539
private $installedScaffolds = [];
3640
private $installedPackages = [];
41+
private $installedJsPackages = [];
3742

3843
public function __construct(FileManager $files)
3944
{
4045
$this->files = $files;
46+
$this->jsPackageManager = new JsPackageManager($files);
4147
}
4248

4349
public static function getCommandName(): string
@@ -75,6 +81,18 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
7581
foreach ($names as $name) {
7682
$this->generateScaffold($name, $io);
7783
}
84+
85+
if ($this->installedJsPackages) {
86+
if ($this->jsPackageManager->isAvailable()) {
87+
$io->comment('Installing JS packages...');
88+
$this->jsPackageManager->install();
89+
90+
$io->comment('Running Webpack Encore...');
91+
$this->jsPackageManager->run('dev');
92+
} else {
93+
$io->warning('Unable to detect JS package manager, you need to run "yarn/npm install".');
94+
}
95+
}
7896
}
7997

8098
public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
@@ -108,15 +126,14 @@ private function generateScaffold(string $name, ConsoleStyle $io): void
108126
$this->generateScaffold($dependent, $io);
109127
}
110128

111-
$io->text("Generating <info>{$name}</info> Scaffold...");
129+
$io->text("Installing <info>{$name}</info> Scaffold...");
112130

113131
// install required packages
114132
foreach ($scaffold['packages'] ?? [] as $package => $env) {
115133
if (!$this->isPackageInstalled($package)) {
116-
$io->text("Installing <comment>{$package}</comment>...");
134+
$io->text("Installing Composer package: <comment>{$package}</comment>...");
117135

118-
// todo composer bin detection
119-
$command = ['composer', 'require', '--no-scripts', 'dev' === $env ? '--dev' : null, $package];
136+
$command = [$this->composerBin(), 'require', '--no-scripts', 'dev' === $env ? '--dev' : null, $package];
120137
$process = new Process(array_filter($command), $this->files->getRootDirectory());
121138

122139
$process->run();
@@ -129,6 +146,16 @@ private function generateScaffold(string $name, ConsoleStyle $io): void
129146
}
130147
}
131148

149+
// install required js packages
150+
foreach ($scaffold['js_packages'] ?? [] as $package => $version) {
151+
if (!\in_array($package, $this->installedJsPackages, true)) {
152+
$io->text("Installing JS package: <comment>{$package}@{$version}</comment>...");
153+
154+
$this->jsPackageManager->add($package, $version);
155+
$this->installedJsPackages[] = $package;
156+
}
157+
}
158+
132159
if (is_dir($scaffold['dir'])) {
133160
$io->text('Copying scaffold files...');
134161

@@ -190,4 +217,17 @@ private function isScaffoldInstalled(string $name): bool
190217
{
191218
return \in_array($name, $this->installedScaffolds, true);
192219
}
220+
221+
private function composerBin(): string
222+
{
223+
if ($this->composerBin) {
224+
return $this->composerBin;
225+
}
226+
227+
if (!$this->composerBin = (new ExecutableFinder())->find('composer')) {
228+
throw new \RuntimeException('Unable to detect composer binary.');
229+
}
230+
231+
return $this->composerBin;
232+
}
193233
}

src/Resources/scaffolds/6.0/bootstrapcss.php

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,11 @@
99
'twig' => 'all',
1010
'encore' => 'all',
1111
],
12+
'js_packages' => [
13+
'bootstrap' => '^5.0.0',
14+
'@popperjs/core' => '^2.0.0',
15+
],
1216
'configure' => function(FileManager $files) {
13-
$packageJson = json_decode($files->getFileContents('package.json'), true);
14-
$devDeps = $packageJson['devDependencies'];
15-
$devDeps['bootstrap'] = '^5.0.0';
16-
$devDeps['@popperjs/core'] = '^2.0.0';
17-
18-
ksort($devDeps);
19-
20-
$packageJson['devDependencies'] = $devDeps;
21-
$files->dumpFile('package.json', json_encode($packageJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
22-
2317
$twig = new YamlSourceManipulator($files->getFileContents('config/packages/twig.yaml'));
2418
$data = $twig->getData();
2519
$data['twig']['form_themes'] = ['bootstrap_5_layout.html.twig'];

src/Test/MakerTestEnvironment.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ public function createInteractiveCommandProcess(string $commandName, array $user
345345
[
346346
'SHELL_INTERACTIVE' => '1',
347347
],
348-
30
348+
40
349349
);
350350

351351
if ($userInputs) {

tests/Maker/MakeScaffoldTest.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
class MakeScaffoldTest extends MakerTestCase
2121
{
22-
private const STARTER_KITS = ['starter-kit'];
23-
2422
protected function setUp(): void
2523
{
2624
parent::setUp();
@@ -45,12 +43,6 @@ public function getTestDetails(): iterable
4543
)
4644
->run(function (MakerTestRunner $runner) use ($name) {
4745
$runner->runMaker([$name]);
48-
49-
if (in_array($name, self::STARTER_KITS, true)) {
50-
$runner->runProcess('yarn install');
51-
$runner->runProcess('yarn dev');
52-
}
53-
5446
$runner->runTests();
5547

5648
$this->assertTrue(true); // successfully ran tests

0 commit comments

Comments
 (0)