Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
56 / 56 |
|
100.00% |
14 / 14 |
CRAP | |
100.00% |
1 / 1 |
ServeCommand | |
100.00% |
56 / 56 |
|
100.00% |
14 / 14 |
28 | |
100.00% |
1 / 1 |
safeHandle | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
getHostSelection | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getPortSelection | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getExecutablePath | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
runServerProcess | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getEnvironmentVariables | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
configureOutput | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
printStartMessage | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getOutputHandler | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
useBasicOutput | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
parseEnvironmentOption | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
checkArgvForOption | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
openInBrowser | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
5 | |||
getOpenCommand | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 |
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'), |
88 | 'HYDE_SERVER_DASHBOARD' => $this->parseEnvironmentOption('dashboard'), |
89 | 'HYDE_PRETTY_URLS' => $this->parseEnvironmentOption('pretty-urls'), |
90 | 'HYDE_PLAY_CDN' => $this->parseEnvironmentOption('play-cdn'), |
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> Use 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 | } |