Skip to content

Commit 571c063

Browse files
emmadesilvagithub-actions[bot]
authored andcommitted
Merge pull request #2298 from hydephp/refactor/move-serve-command-to-realtime-compiler-package
[2.x] Move the serve command to the realtime compiler package hydephp/develop@ced3fe5
1 parent d7ac12b commit 571c063

File tree

2 files changed

+251
-0
lines changed

2 files changed

+251
-0
lines changed
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hyde\RealtimeCompiler\Console\Commands;
6+
7+
use Closure;
8+
use Hyde\Facades\Filesystem;
9+
use Hyde\Hyde;
10+
use Hyde\Facades\Config;
11+
use Illuminate\Contracts\Process\InvokedProcess;
12+
use Illuminate\Support\Arr;
13+
use Illuminate\Support\Sleep;
14+
use InvalidArgumentException;
15+
use Hyde\Console\Concerns\Command;
16+
use Hyde\RealtimeCompiler\ConsoleOutput;
17+
use Illuminate\Support\Facades\Process;
18+
19+
use function rtrim;
20+
use function sprintf;
21+
use function in_array;
22+
use function str_replace;
23+
use function class_exists;
24+
25+
/**
26+
* Start the realtime compiler server.
27+
*
28+
* @see https://github.com/hydephp/realtime-compiler
29+
*/
30+
class ServeCommand extends Command
31+
{
32+
/** @var string */
33+
protected $signature = 'serve
34+
{--host= : <comment>[default: "localhost"]</comment>}}
35+
{--port= : <comment>[default: 8080]</comment>}
36+
{--save-preview= : Should the served page be saved to disk? (Overrides config setting)}
37+
{--dashboard= : Enable the realtime compiler dashboard. (Overrides config setting)}
38+
{--pretty-urls= : Enable pretty URLs. (Overrides config setting)}
39+
{--play-cdn= : Enable the Tailwind Play CDN. (Overrides config setting)}
40+
{--open=false : Open the site preview in the browser.}
41+
{--vite : Enable Vite for Hot Module Replacement (HMR)}
42+
';
43+
44+
/** @var string */
45+
protected $description = 'Start the realtime compiler server';
46+
47+
protected ConsoleOutput $console;
48+
49+
protected InvokedProcess $server;
50+
protected ?InvokedProcess $vite = null;
51+
52+
public function safeHandle(): int
53+
{
54+
$this->configureOutput();
55+
$this->printStartMessage();
56+
57+
if ($this->option('open') !== 'false') {
58+
$this->openInBrowser((string) $this->option('open'));
59+
}
60+
61+
if ($this->option('vite')) {
62+
$this->runViteProcess();
63+
}
64+
65+
$this->runServerProcess(sprintf('php -S %s:%d %s',
66+
$this->getHostSelection(),
67+
$this->getPortSelection(),
68+
escapeshellarg($this->getExecutablePath()),
69+
));
70+
71+
$this->handleRunningProcesses();
72+
73+
if ($this->option('vite')) {
74+
$this->cleanupViteHotFile();
75+
}
76+
77+
return Command::SUCCESS;
78+
}
79+
80+
protected function getHostSelection(): string
81+
{
82+
return (string) $this->option('host') ?: Config::getString('hyde.server.host', 'localhost');
83+
}
84+
85+
protected function getPortSelection(): int
86+
{
87+
return (int) ($this->option('port') ?: Config::getInt('hyde.server.port', 8080));
88+
}
89+
90+
protected function getExecutablePath(): string
91+
{
92+
return Hyde::path('vendor/hyde/realtime-compiler/bin/server.php');
93+
}
94+
95+
protected function runServerProcess(string $command): void
96+
{
97+
$this->server = Process::forever()->env($this->getEnvironmentVariables())->start($command, $this->getOutputHandler());
98+
}
99+
100+
protected function getEnvironmentVariables(): array
101+
{
102+
return Arr::whereNotNull([
103+
'HYDE_SERVER_REQUEST_OUTPUT' => ! $this->option('no-ansi'),
104+
'HYDE_SERVER_SAVE_PREVIEW' => $this->parseEnvironmentOption('save-preview'),
105+
'HYDE_SERVER_DASHBOARD' => $this->parseEnvironmentOption('dashboard'),
106+
'HYDE_PRETTY_URLS' => $this->parseEnvironmentOption('pretty-urls'),
107+
'HYDE_PLAY_CDN' => $this->parseEnvironmentOption('play-cdn'),
108+
]);
109+
}
110+
111+
protected function configureOutput(): void
112+
{
113+
if (! $this->useBasicOutput()) {
114+
$this->console = new ConsoleOutput($this->output->isVerbose());
115+
}
116+
}
117+
118+
protected function printStartMessage(): void
119+
{
120+
$this->useBasicOutput()
121+
? $this->output->writeln('<info>Starting the HydeRC server...</info> Use Ctrl+C to stop')
122+
: $this->console->printStartMessage($this->getHostSelection(), $this->getPortSelection(), $this->getEnvironmentVariables(), $this->option('vite'));
123+
}
124+
125+
protected function getOutputHandler(): Closure
126+
{
127+
return $this->useBasicOutput() ? function (string $type, string $line): void {
128+
$this->output->write($line);
129+
} : $this->console->getFormatter();
130+
}
131+
132+
protected function useBasicOutput(): bool
133+
{
134+
return $this->option('no-ansi') || ! class_exists(ConsoleOutput::class);
135+
}
136+
137+
protected function parseEnvironmentOption(string $name): ?string
138+
{
139+
$value = $this->option($name) ?? $this->checkArgvForOption($name);
140+
141+
if ($value !== null) {
142+
return match ($value) {
143+
'true', '' => 'enabled',
144+
'false' => 'disabled',
145+
default => throw new InvalidArgumentException(sprintf('Invalid boolean value for --%s option.', $name))
146+
};
147+
}
148+
149+
return null;
150+
}
151+
152+
/** Fallback check so that an environment option without a value is acknowledged as true. */
153+
protected function checkArgvForOption(string $name): ?string
154+
{
155+
if (isset($_SERVER['argv'])) {
156+
if (in_array("--$name", $_SERVER['argv'], true)) {
157+
return 'true';
158+
}
159+
}
160+
161+
return null;
162+
}
163+
164+
protected function openInBrowser(string $path = '/'): void
165+
{
166+
$binary = $this->getOpenCommand(PHP_OS_FAMILY);
167+
168+
$command = sprintf('%s http://%s:%d', $binary, $this->getHostSelection(), $this->getPortSelection());
169+
$command = rtrim("$command/$path", '/');
170+
171+
$process = $binary ? Process::command($command)->run() : null;
172+
173+
if (! $process || $process->failed()) {
174+
$this->warn('Unable to open the site preview in the browser on your system:');
175+
$this->line(sprintf(' %s', str_replace("\n", "\n ", $process ? $process->errorOutput() : "Missing suitable 'open' binary.")));
176+
$this->newLine();
177+
}
178+
}
179+
180+
protected function getOpenCommand(string $osFamily): ?string
181+
{
182+
return match ($osFamily) {
183+
'Windows' => 'start',
184+
'Darwin' => 'open',
185+
'Linux' => 'xdg-open',
186+
default => null
187+
};
188+
}
189+
190+
protected function runViteProcess(): void
191+
{
192+
if (! $this->isPortAvailable(5173)) {
193+
throw new InvalidArgumentException(
194+
'Unable to start Vite server: Port 5173 is already in use. '.
195+
'Please stop any other Vite processes and try again.'
196+
);
197+
}
198+
199+
Filesystem::touch('app/storage/framework/runtime/vite.hot');
200+
201+
$this->vite = Process::forever()->start('npm run dev');
202+
}
203+
204+
protected function handleRunningProcesses(): void
205+
{
206+
while ($this->server->running()) {
207+
$this->handleViteOutput();
208+
209+
Sleep::for(100)->milliseconds();
210+
}
211+
}
212+
213+
protected function handleViteOutput(): void
214+
{
215+
if ($this->vite?->running()) {
216+
$output = $this->vite->latestOutput();
217+
218+
if ($output) {
219+
$this->output->write($output);
220+
}
221+
}
222+
}
223+
224+
/** @experimental This feature may be removed before the final release. */
225+
protected function isPortAvailable(int $port): bool
226+
{
227+
$addresses = ['localhost', '127.0.0.1'];
228+
229+
foreach ($addresses as $address) {
230+
$socket = @fsockopen($address, $port, $errno, $errstr, 1);
231+
if ($socket !== false) {
232+
fclose($socket);
233+
234+
return false;
235+
}
236+
}
237+
238+
return true;
239+
}
240+
241+
protected function cleanupViteHotFile(): void
242+
{
243+
$hotFile = 'app/storage/framework/runtime/vite.hot';
244+
245+
if (Filesystem::exists($hotFile)) {
246+
Filesystem::unlinkIfExists($hotFile);
247+
}
248+
}
249+
}

src/RealtimeCompilerServiceProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Hyde\RealtimeCompiler\Http\LiveEditController;
1010
use Hyde\RealtimeCompiler\Http\VirtualRouteController;
1111
use Hyde\RealtimeCompiler\Console\Commands\HerdInstallCommand;
12+
use Hyde\RealtimeCompiler\Console\Commands\ServeCommand;
1213

1314
class RealtimeCompilerServiceProvider extends ServiceProvider
1415
{
@@ -19,6 +20,7 @@ public function register(): void
1920
if ($this->app->runningInConsole()) {
2021
$this->commands([
2122
HerdInstallCommand::class,
23+
ServeCommand::class,
2224
]);
2325
}
2426
}

0 commit comments

Comments
 (0)