Skip to content

Commit 5fd8e84

Browse files
authored
Merge pull request #256 from alanpoulain/dependencies-step
Replace the bower install step to manage yarn and npm as well
2 parents 8f9066e + 3e92bf5 commit 5fd8e84

File tree

9 files changed

+276
-82
lines changed

9 files changed

+276
-82
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ interface Step
3434

3535
Steps are very granular, thus extremely easy to write and test. For example:
3636

37-
- `LoadConfig`: loads the `couscous.yml` config file
38-
- `RunBowerInstall`
37+
- `LoadConfig`: load the `couscous.yml` config file
38+
- `InstallDependencies`: install the dependencies (using yarn, npm or bower)
3939
- `LoadMarkdownFiles`: load the content of all the `*.md` files in memory
4040
- `RenderMarkdown`: render the markdown content
4141
- `WriteFiles`: write the in-memory processed files to the target directory

docs/templates.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ Then you can use it in the layouts:
148148
All your Markdown links will be rewritten by Couscous to work. However, make sure you write relative links.
149149
A good rule of thumb is: **if it works on GitHub.com, it will work with Couscous**.
150150

151-
## Bower
151+
## Dependencies
152152

153-
If a `bower.json` file is present in the `website/` directory, dependencies will be
154-
installed automatically.
153+
If a `package.json` or a `bower.json` file is present in the `website/` directory, dependencies will be
154+
installed automatically by using `yarn`, `npm` or `bower`.
155155

156-
In that case, you need [to have Bower installed](https://bower.io/). If you don't have a `bower.json`, you don't need to install Bower.
156+
In that case, you need to have one of these tools installed. If you don't have a dependency file, you don't need to install one of them.

src/Application/config.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
DI\get('Couscous\Module\Template\Step\FetchRemoteTemplate'),
1818
DI\get('Couscous\Module\Template\Step\ValidateTemplateDirectory'),
1919

20-
DI\get('Couscous\Module\Bower\Step\RunBowerInstall'),
20+
DI\get('Couscous\Module\Dependencies\Step\InstallDependencies'),
2121

2222
DI\get('Couscous\Module\Markdown\Step\LoadMarkdownFiles'),
2323
DI\get('Couscous\Module\Template\Step\LoadAssets'),

src/CommandRunner/CommandRunner.php

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
class CommandRunner
1111
{
1212
/**
13-
* Runs a command.
14-
*
13+
* Run a command.
1514
*
1615
* @param string $command The command to be executed.
1716
*
@@ -21,7 +20,7 @@ class CommandRunner
2120
*/
2221
public function run($command)
2322
{
24-
if (PHP_OS === 'WINNT') {
23+
if ($this->isWindows()) {
2524
exec($command, $output, $returnValue);
2625
} else {
2726
exec($command . ' 2>&1', $output, $returnValue);
@@ -35,4 +34,28 @@ public function run($command)
3534

3635
return $output;
3736
}
37+
38+
/**
39+
* Check if a command exists.
40+
*
41+
* @param string $command
42+
*
43+
* @return bool
44+
*/
45+
public function commandExists($command)
46+
{
47+
$exists = $this->isWindows() ? 'where' : 'command -v';
48+
49+
return !empty(exec("$exists $command"));
50+
}
51+
52+
/**
53+
* Check if the OS is Windows.
54+
*
55+
* @return bool
56+
*/
57+
private function isWindows()
58+
{
59+
return stripos(PHP_OS, 'WIN') === 0;
60+
}
3861
}

src/Module/Bower/Step/RunBowerInstall.php

Lines changed: 0 additions & 71 deletions
This file was deleted.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
namespace Couscous\Module\Dependencies\Step;
4+
5+
use Couscous\CommandRunner\CommandRunner;
6+
use Couscous\Model\Project;
7+
use Couscous\Step;
8+
use Psr\Log\LoggerInterface;
9+
use Symfony\Component\Filesystem\Filesystem;
10+
11+
/**
12+
* Install dependencies using yarn, npm or bower.
13+
*
14+
* @author Matthieu Napoli <[email protected]>
15+
*/
16+
class InstallDependencies implements Step
17+
{
18+
/**
19+
* @var Filesystem
20+
*/
21+
private $filesystem;
22+
23+
/**
24+
* @var CommandRunner
25+
*/
26+
private $commandRunner;
27+
28+
/**
29+
* @var LoggerInterface
30+
*/
31+
private $logger;
32+
33+
public function __construct(
34+
Filesystem $filesystem,
35+
CommandRunner $commandRunner,
36+
LoggerInterface $logger
37+
) {
38+
$this->filesystem = $filesystem;
39+
$this->commandRunner = $commandRunner;
40+
$this->logger = $logger;
41+
}
42+
43+
public function __invoke(Project $project)
44+
{
45+
$canUseYarn = $this->canUseYarn($project);
46+
$canUseNpm = $this->canUseNpm($project);
47+
$canUseBower = $this->canUseBower($project);
48+
49+
if ($project->regenerate || (!$canUseYarn && !$canUseNpm && !$canUseBower)) {
50+
return;
51+
}
52+
53+
$command = 'bower';
54+
if ($canUseNpm) {
55+
$command = 'npm';
56+
}
57+
// Use yarn preferably
58+
if ($canUseYarn) {
59+
$command = 'yarn';
60+
}
61+
62+
$this->logger->notice("Executing \"$command install\"");
63+
64+
$result = $this->commandRunner->run(sprintf(
65+
"cd \"%s\" && $command install",
66+
$project->metadata['template.directory']
67+
));
68+
69+
if ($result) {
70+
$this->logger->info($result);
71+
}
72+
}
73+
74+
/**
75+
* @return bool
76+
*/
77+
private function canUseYarn(Project $project)
78+
{
79+
return $this->hasPackageJson($project) && $this->commandRunner->commandExists('yarn');
80+
}
81+
82+
/**
83+
* @return bool
84+
*/
85+
private function canUseNpm(Project $project)
86+
{
87+
return $this->hasPackageJson($project) && $this->commandRunner->commandExists('npm');
88+
}
89+
90+
/**
91+
* @return bool
92+
*/
93+
private function canUseBower(Project $project)
94+
{
95+
return $this->hasBowerJson($project) && $this->commandRunner->commandExists('bower');
96+
}
97+
98+
/**
99+
* @return bool
100+
*/
101+
private function hasPackageJson(Project $project)
102+
{
103+
if (!$project->metadata['template.directory']) {
104+
return false;
105+
}
106+
107+
$filename = $project->metadata['template.directory'].'/package.json';
108+
109+
return $this->filesystem->exists($filename);
110+
}
111+
112+
/**
113+
* @return bool
114+
*/
115+
private function hasBowerJson(Project $project)
116+
{
117+
if (!$project->metadata['template.directory']) {
118+
return false;
119+
}
120+
121+
$filename = $project->metadata['template.directory'].'/bower.json';
122+
123+
return $this->filesystem->exists($filename);
124+
}
125+
}

tests/FunctionalTest/CommandRunner/CommandRunnerTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,30 @@ public function failure_executing_command_throws_runtime_exception()
4343

4444
$this->commandRunner->run($command);
4545
}
46+
47+
/**
48+
* @test
49+
*/
50+
public function successful_command_exists()
51+
{
52+
$command = 'echo';
53+
$expected = true;
54+
55+
$output = $this->commandRunner->commandExists($command);
56+
57+
$this->assertEquals($expected, $output);
58+
}
59+
60+
/**
61+
* @test
62+
*/
63+
public function failure_command_exists()
64+
{
65+
$command = 'not_existing_command';
66+
$expected = false;
67+
68+
$output = $this->commandRunner->commandExists($command);
69+
70+
$this->assertEquals($expected, $output);
71+
}
4672
}

0 commit comments

Comments
 (0)