diff --git a/cli/Valet/Composer.php b/cli/Valet/Composer.php index 7f14c96e5..60068d52c 100644 --- a/cli/Valet/Composer.php +++ b/cli/Valet/Composer.php @@ -14,15 +14,14 @@ public function installed(string $namespacedPackage): bool { $result = $this->cli->runAsUser("composer global show --format json -- $namespacedPackage"); - if (starts_with($result, 'Changed current')) { - $result = strstr($result, '{'); - } - - // should be a json response, but if not installed then "not found" if (str_contains($result, 'InvalidArgumentException') && str_contains($result, 'not found')) { return false; } + if (starts_with($result, 'Changed current')) { + $result = strstr($result, '{'); + } + $details = json_decode($result, true); return ! empty($details); @@ -38,4 +37,22 @@ public function installOrFail(string $namespacedPackage): void throw new DomainException('Composer was unable to install ['.$namespacedPackage.'].'); }); } + + public function installedVersion(string $namespacedPackage): ?string + { + $result = $this->cli->runAsUser("composer global show --format json -- $namespacedPackage"); + + if (str_contains($result, 'InvalidArgumentException') && str_contains($result, 'not found')) { + return null; + } + + if (starts_with($result, 'Changed current')) { + $result = strstr($result, '{'); + } + + $details = json_decode($result, true); + $versions = $details['versions']; + + return reset($versions); + } } diff --git a/cli/Valet/Expose.php b/cli/Valet/Expose.php index 969124ad8..cdec1002c 100644 --- a/cli/Valet/Expose.php +++ b/cli/Valet/Expose.php @@ -2,15 +2,52 @@ namespace Valet; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\ConnectException; + class Expose { - public function __construct(public Composer $composer) + public function __construct(public Composer $composer, public CommandLine $cli) { } - public function currentTunnelUrl(?string $domain = null): string + public function currentTunnelUrl(?string $domain = null): ?string + { + $endpoint = 'http://127.0.0.1:4040/api/tunnels'; + + try { + $response = retry(20, function () use ($endpoint, $domain) { + $body = json_decode((new Client())->get($endpoint)->getBody()); + + if (isset($body->tunnels) && count($body->tunnels) > 0) { + if ($tunnelUrl = $this->findHttpTunnelUrl($body->tunnels, $domain)) { + return $tunnelUrl; + } + } + }, 250); + + if (! empty($response)) { + return $response; + } + + return warning('The project '.$domain.' cannot be found as an Expose share.'); + } catch (ConnectException $e) { + return warning('There is no Expose instance running.'); + } + } + + /** + * Find the HTTP tunnel URL from the list of tunnels. + */ + public function findHttpTunnelUrl(array $tunnels, string $domain): ?string { - return '@todo'; + foreach ($tunnels as $tunnel) { + if (strpos($tunnel, strtolower($domain))) { + return $tunnel; + } + } + + return null; } /** @@ -21,6 +58,14 @@ public function installed(): bool return $this->composer->installed('beyondcode/expose'); } + /** + * Return which version of Expose is installed. + */ + public function installedVersion(): ?string + { + return $this->composer->installedVersion('beyondcode/expose'); + } + /** * Make sure Expose is installed. */ diff --git a/cli/app.php b/cli/app.php index e82636bd4..a7f988285 100644 --- a/cli/app.php +++ b/cli/app.php @@ -345,7 +345,7 @@ function (ConsoleCommandEvent $event) { switch ($tool) { case 'expose': - output(Expose::currentTunnelUrl(Site::domain($domain))); + output(Expose::currentTunnelUrl($domain ?: Site::host(getcwd()))); break; case 'ngrok': try { @@ -380,6 +380,10 @@ function (ConsoleCommandEvent $event) { if ($tool === 'expose') { if (Expose::installed()) { + // @todo: Check it's the right version (has /api/tunnels/) + // E.g. if (Expose::installedVersion) + // if (version_compare(Expose::installedVersion(), $minimumExposeVersion) < 0) { + // prompt them to upgrade return; } diff --git a/cli/includes/helpers.php b/cli/includes/helpers.php index bcdf978bf..f9d0ce89e 100644 --- a/cli/includes/helpers.php +++ b/cli/includes/helpers.php @@ -88,7 +88,7 @@ function testing(): bool /** * Output the given text to the console. */ -function output(string $output = ''): void +function output(?string $output = ''): void { writer()->writeln($output); } diff --git a/tests/ComposerTest.php b/tests/ComposerTest.php index 44fe9f374..ad3642891 100644 --- a/tests/ComposerTest.php +++ b/tests/ComposerTest.php @@ -57,4 +57,24 @@ public function test_install_or_fail_will_install_composer_package() resolve(Composer::class)->installOrFail('beyondcode/expose'); } + + public function test_installed_version_returns_null_when_given_package_is_not_installed() + { + $cli = Mockery::mock(CommandLine::class); + $cli->shouldReceive('runAsUser')->once()->with('composer global show --format json -- beyondcode/expose') + ->andReturn("Changed current directory to /Users/mattstauffer/.composer\n\n[InvalidArgumentException]\nPackage beyondcode/expose not found"); + swap(CommandLine::class, $cli); + + $this->assertNull(resolve(Composer::class)->installedVersion('beyondcode/expose')); + } + + public function test_installed_version_returns_version_when_package_is_installed() + { + $cli = Mockery::mock(CommandLine::class); + $cli->shouldReceive('runAsUser')->once()->with('composer global show --format json -- beyondcode/expose') + ->andReturn('{"versions":["1.4.2"]}'); + swap(CommandLine::class, $cli); + + $this->assertEquals('1.4.2', resolve(Composer::class)->installedVersion('beyondcode/expose')); + } }