diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ead8bb0b1ba..167acc38c8b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -13,6 +13,7 @@ This serves two purposes: - Adds a new fancy output for the realtime compiler serve command in https://github.com/hydephp/develop/pull/1444 - Added support for dot notation in the Yaml configuration files in https://github.com/hydephp/develop/pull/1478 - Added a config option to customize automatic sidebar navigation group names in https://github.com/hydephp/develop/pull/1481 +- Added a new `hyde serve --open` option to automatically open the site in the browser in https://github.com/hydephp/develop/pull/1483 ### Changed - The `docs.sidebar.footer` config option now accepts a Markdown string to replace the default footer in https://github.com/hydephp/develop/pull/1477 diff --git a/packages/framework/src/Console/Commands/ServeCommand.php b/packages/framework/src/Console/Commands/ServeCommand.php index a576864477e..9558fa4f53b 100644 --- a/packages/framework/src/Console/Commands/ServeCommand.php +++ b/packages/framework/src/Console/Commands/ServeCommand.php @@ -14,6 +14,7 @@ use Illuminate\Support\Facades\Process; use function sprintf; +use function str_replace; use function class_exists; /** @@ -31,6 +32,7 @@ class ServeCommand extends Command {--dashboard= : Enable the realtime compiler dashboard. (Overrides config setting)} {--pretty-urls= : Enable pretty URLs. (Overrides config setting)} {--play-cdn= : Enable the Tailwind Play CDN. (Overrides config setting)} + {--open : Open the site preview in the browser.} '; /** @var string */ @@ -43,6 +45,10 @@ public function safeHandle(): int $this->configureOutput(); $this->printStartMessage(); + if ($this->option('open')) { + $this->openInBrowser(); + } + $this->runServerProcess(sprintf('php -S %s:%d %s', $this->getHostSelection(), $this->getPortSelection(), @@ -135,4 +141,22 @@ protected function checkArgvForOption(string $name): ?string return null; } + + protected function openInBrowser(): void + { + $command = match (PHP_OS_FAMILY) { + 'Windows' => 'start', + 'Darwin' => 'open', + 'Linux' => 'xdg-open', + default => null + }; + + $process = $command ? Process::command(sprintf('%s http://%s:%d', $command, $this->getHostSelection(), $this->getPortSelection()))->run() : null; + + if (! $process || $process->failed()) { + $this->warn('Unable to open the site preview in the browser on your system:'); + $this->line(sprintf(' %s', str_replace("\n", "\n ", $process ? $process->errorOutput() : "Missing suitable 'open' binary."))); + $this->newLine(); + } + } } diff --git a/packages/framework/tests/Unit/ServeCommandOptionsUnitTest.php b/packages/framework/tests/Unit/ServeCommandOptionsUnitTest.php index 935d9793198..9b73aa63aa0 100644 --- a/packages/framework/tests/Unit/ServeCommandOptionsUnitTest.php +++ b/packages/framework/tests/Unit/ServeCommandOptionsUnitTest.php @@ -4,8 +4,14 @@ namespace Hyde\Framework\Testing\Unit; +use Mockery; use Hyde\Testing\UnitTestCase; +use Hyde\Foundation\HydeKernel; +use Illuminate\Console\OutputStyle; use Hyde\Console\Commands\ServeCommand; +use Illuminate\Support\Facades\Process; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * @covers \Hyde\Console\Commands\ServeCommand @@ -196,6 +202,91 @@ public function test_checkArgvForOption() $_SERVER = $serverBackup; } + public function testWithOpenArgument() + { + HydeKernel::setInstance(new HydeKernel()); + + $command = new class(['open' => true]) extends ServeCommandMock + { + public bool $openInBrowserCalled = false; + + // Void unrelated methods + protected function configureOutput(): void + { + } + + protected function printStartMessage(): void + { + } + + protected function runServerProcess(string $command): void + { + } + + protected function openInBrowser(): void + { + $this->openInBrowserCalled = true; + } + }; + + $command->safeHandle(); + $this->assertTrue($command->openInBrowserCalled); + } + + public function test_openInBrowser() + { + $output = $this->createMock(OutputStyle::class); + $output->expects($this->never())->method('writeln'); + + $command = $this->getMock(['--open' => true]); + $command->setOutput($output); + + $binary = match (PHP_OS_FAMILY) { + 'Darwin' => 'open', + 'Windows' => 'start', + default => 'xdg-open', + }; + + Process::shouldReceive('command')->once()->with("$binary http://localhost:8080")->andReturnSelf(); + Process::shouldReceive('run')->once()->andReturnSelf(); + Process::shouldReceive('failed')->once()->andReturn(false); + + $command->openInBrowser(); + } + + public function test_openInBrowserThatFails() + { + $output = Mockery::mock(OutputStyle::class); + $output->shouldReceive('getFormatter')->andReturn($this->createMock(OutputFormatterInterface::class)); + + $warning = 'Unable to open the site preview in the browser on your system:'; + $context = ' Missing suitable \'open\' binary.'; + + $output->shouldReceive('writeln')->once()->with($warning, OutputInterface::VERBOSITY_NORMAL); + $output->shouldReceive('writeln')->once()->with($context, OutputInterface::VERBOSITY_NORMAL); + $output->shouldReceive('newLine')->once(); + + $command = $this->getMock(['--open' => true]); + $command->setOutput($output); + + $binary = match (PHP_OS_FAMILY) { + 'Darwin' => 'open', + 'Windows' => 'start', + default => 'xdg-open', + }; + + Process::shouldReceive('command')->once()->with("$binary http://localhost:8080")->andReturnSelf(); + Process::shouldReceive('run')->once()->andReturnSelf(); + Process::shouldReceive('failed')->once()->andReturn(true); + Process::shouldReceive('errorOutput')->once()->andReturn("Missing suitable 'open' binary."); + + $command->openInBrowser(); + + Mockery::close(); + + $this->assertTrue(true); + } + protected function getMock(array $options = []): ServeCommandMock { return new ServeCommandMock($options); @@ -208,6 +299,7 @@ protected function getMock(array $options = []): ServeCommandMock * @method getEnvironmentVariables * @method parseEnvironmentOption(string $name) * @method checkArgvForOption(string $name) + * @method openInBrowser() */ class ServeCommandMock extends ServeCommand {