Passed
Push — master ( 2ed18b...d69bd8 )
by Caen
04:33 queued 17s
created

ServeCommand::getOpenCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 2
b 0
f 0
nc 1
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Console\Commands;
6
7
use Closure;
8
use Hyde\Hyde;
9
use Hyde\Facades\Config;
10
use Illuminate\Support\Arr;
11
use InvalidArgumentException;
12
use Hyde\Console\Concerns\Command;
13
use Hyde\RealtimeCompiler\ConsoleOutput;
14
use Illuminate\Support\Facades\Process;
15
16
use function rtrim;
17
use function sprintf;
18
use function in_array;
19
use function str_replace;
20
use function class_exists;
21
22
/**
23
 * Start the realtime compiler server.
24
 *
25
 * @see https://github.com/hydephp/realtime-compiler
26
 */
27
class ServeCommand extends Command
28
{
29
    /** @var string */
30
    protected $signature = 'serve
31
        {--host= : <comment>[default: "localhost"]</comment>}}
32
        {--port= : <comment>[default: 8080]</comment>}
33
        {--save-preview= : Should the served page be saved to disk? (Overrides config setting)}
34
        {--dashboard= : Enable the realtime compiler dashboard. (Overrides config setting)}
35
        {--pretty-urls= : Enable pretty URLs. (Overrides config setting)}
36
        {--play-cdn= : Enable the Tailwind Play CDN. (Overrides config setting)}
37
        {--open=false : Open the site preview in the browser.}
38
    ';
39
40
    /** @var string */
41
    protected $description = 'Start the realtime compiler server';
42
43
    protected ConsoleOutput $console;
44
45
    public function safeHandle(): int
46
    {
47
        $this->configureOutput();
48
        $this->printStartMessage();
49
50
        if ($this->option('open') !== 'false') {
51
            $this->openInBrowser((string) $this->option('open'));
52
        }
53
54
        $this->runServerProcess(sprintf('php -S %s:%d %s',
55
            $this->getHostSelection(),
56
            $this->getPortSelection(),
57
            $this->getExecutablePath()
58
        ));
59
60
        return Command::SUCCESS;
61
    }
62
63
    protected function getHostSelection(): string
64
    {
65
        return (string) $this->option('host') ?: Config::getString('hyde.server.host', 'localhost');
66
    }
67
68
    protected function getPortSelection(): int
69
    {
70
        return (int) ($this->option('port') ?: Config::getInt('hyde.server.port', 8080));
71
    }
72
73
    protected function getExecutablePath(): string
74
    {
75
        return Hyde::path('vendor/hyde/realtime-compiler/bin/server.php');
76
    }
77
78
    protected function runServerProcess(string $command): void
79
    {
80
        Process::forever()->env($this->getEnvironmentVariables())->run($command, $this->getOutputHandler());
81
    }
82
83
    protected function getEnvironmentVariables(): array
84
    {
85
        return Arr::whereNotNull([
86
            'HYDE_SERVER_REQUEST_OUTPUT' => ! $this->option('no-ansi'),
87
            'HYDE_SERVER_SAVE_PREVIEW' => $this->parseEnvironmentOption('save-preview'),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->parseEnvironmentOption('save-preview') targeting Hyde\Console\Commands\Se...arseEnvironmentOption() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
88
            'HYDE_SERVER_DASHBOARD' => $this->parseEnvironmentOption('dashboard'),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->parseEnvironmentOption('dashboard') targeting Hyde\Console\Commands\Se...arseEnvironmentOption() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
89
            'HYDE_PRETTY_URLS' => $this->parseEnvironmentOption('pretty-urls'),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->parseEnvironmentOption('pretty-urls') targeting Hyde\Console\Commands\Se...arseEnvironmentOption() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
90
            'HYDE_PLAY_CDN' => $this->parseEnvironmentOption('play-cdn'),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->parseEnvironmentOption('play-cdn') targeting Hyde\Console\Commands\Se...arseEnvironmentOption() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
91
        ]);
92
    }
93
94
    protected function configureOutput(): void
95
    {
96
        if (! $this->useBasicOutput()) {
97
            $this->console = new ConsoleOutput($this->output->isVerbose());
98
        }
99
    }
100
101
    protected function printStartMessage(): void
102
    {
103
        $this->useBasicOutput()
104
            ? $this->output->writeln('<info>Starting the HydeRC server...</info> Press Ctrl+C to stop')
105
            : $this->console->printStartMessage($this->getHostSelection(), $this->getPortSelection(), $this->getEnvironmentVariables());
106
    }
107
108
    protected function getOutputHandler(): Closure
109
    {
110
        return $this->useBasicOutput() ? function (string $type, string $line): void {
111
            $this->output->write($line);
112
        } : $this->console->getFormatter();
113
    }
114
115
    protected function useBasicOutput(): bool
116
    {
117
        return $this->option('no-ansi') || ! class_exists(ConsoleOutput::class);
118
    }
119
120
    protected function parseEnvironmentOption(string $name): ?string
121
    {
122
        $value = $this->option($name) ?? $this->checkArgvForOption($name);
123
124
        if ($value !== null) {
125
            return match ($value) {
126
                'true', '' => 'enabled',
127
                'false' => 'disabled',
128
                default => throw new InvalidArgumentException(sprintf('Invalid boolean value for --%s option.', $name))
129
            };
130
        }
131
132
        return null;
133
    }
134
135
    /** Fallback check so that an environment option without a value is acknowledged as true. */
136
    protected function checkArgvForOption(string $name): ?string
137
    {
138
        if (isset($_SERVER['argv'])) {
139
            if (in_array("--$name", $_SERVER['argv'], true)) {
140
                return 'true';
141
            }
142
        }
143
144
        return null;
145
    }
146
147
    protected function openInBrowser(string $path = '/'): void
148
    {
149
        $binary = $this->getOpenCommand(PHP_OS_FAMILY);
150
151
        $command = sprintf('%s http://%s:%d', $binary, $this->getHostSelection(), $this->getPortSelection());
152
        $command = rtrim("$command/$path", '/');
153
154
        $process = $binary ? Process::command($command)->run() : null;
155
156
        if (! $process || $process->failed()) {
157
            $this->warn('Unable to open the site preview in the browser on your system:');
158
            $this->line(sprintf('  %s', str_replace("\n", "\n  ", $process ? $process->errorOutput() : "Missing suitable 'open' binary.")));
159
            $this->newLine();
160
        }
161
    }
162
163
    protected function getOpenCommand(string $osFamily): ?string
164
    {
165
        return match ($osFamily) {
166
            'Windows' => 'start',
167
            'Darwin' => 'open',
168
            'Linux' => 'xdg-open',
169
            default => null
170
        };
171
    }
172
}
173