diff --git a/cli/Valet/DnsMasq.php b/cli/Valet/DnsMasq.php index 60b3ad187..790a33ac1 100644 --- a/cli/Valet/DnsMasq.php +++ b/cli/Valet/DnsMasq.php @@ -4,13 +4,15 @@ class DnsMasq { - public $dnsmasqMasterConfigFile = BREW_PREFIX.'/etc/dnsmasq.conf'; + public $dnsmasqMasterConfigFile = BREW_PREFIX . '/etc/dnsmasq.conf'; - public $dnsmasqSystemConfDir = BREW_PREFIX.'/etc/dnsmasq.d'; + public $dnsmasqSystemConfDir = BREW_PREFIX . '/etc/dnsmasq.d'; public $resolverPath = '/etc/resolver'; - public function __construct(public Brew $brew, public CommandLine $cli, public Filesystem $files, public Configuration $configuration) {} + public function __construct(public Brew $brew, public CommandLine $cli, public Filesystem $files, public Configuration $configuration) + { + } /** * Install and configure DnsMasq. @@ -30,7 +32,7 @@ public function install(string $tld = 'test'): void $this->brew->restartService('dnsmasq'); - info('Valet is configured to serve for TLD [.'.$tld.']'); + info('Valet is configured to serve for TLD [.' . $tld . ']'); } /** @@ -40,13 +42,13 @@ public function uninstall(): void { $this->brew->stopService('dnsmasq'); $this->brew->uninstallFormula('dnsmasq'); - $this->cli->run('rm -rf '.BREW_PREFIX.'/etc/dnsmasq.d/dnsmasq-valet.conf'); + $this->cli->run('rm -rf ' . BREW_PREFIX . '/etc/dnsmasq.d/dnsmasq-valet.conf'); // As Laravel Herd uses the same DnsMasq resolver, we should only // delete it if Herd is not installed. - if (! $this->files->exists('/Applications/Herd.app')) { + if (!$this->files->exists('/Applications/Herd.app')) { $tld = $this->configuration->read()['tld']; - $this->files->unlink($this->resolverPath.'/'.$tld); + $this->files->unlink($this->resolverPath . '/' . $tld); } } @@ -76,10 +78,10 @@ public function ensureUsingDnsmasqDForConfigs(): void // set primary config to look for configs in /usr/local/etc/dnsmasq.d/*.conf $contents = $this->files->get($this->dnsmasqMasterConfigFile); // ensure the line we need to use is present, and uncomment it if needed - if (strpos($contents, 'conf-dir='.BREW_PREFIX.'/etc/dnsmasq.d/,*.conf') === false) { - $contents .= PHP_EOL.'conf-dir='.BREW_PREFIX.'/etc/dnsmasq.d/,*.conf'.PHP_EOL; + if (strpos($contents, 'conf-dir=' . BREW_PREFIX . '/etc/dnsmasq.d/,*.conf') === false) { + $contents .= PHP_EOL . 'conf-dir=' . BREW_PREFIX . '/etc/dnsmasq.d/,*.conf' . PHP_EOL; } - $contents = str_replace('#conf-dir='.BREW_PREFIX.'/etc/dnsmasq.d/,*.conf', 'conf-dir='.BREW_PREFIX.'/etc/dnsmasq.d/,*.conf', $contents); + $contents = str_replace('#conf-dir=' . BREW_PREFIX . '/etc/dnsmasq.d/,*.conf', 'conf-dir=' . BREW_PREFIX . '/etc/dnsmasq.d/,*.conf', $contents); // remove entries used by older Valet versions: $contents = preg_replace('/^conf-file.*valet.*$/m', '', $contents); @@ -88,7 +90,7 @@ public function ensureUsingDnsmasqDForConfigs(): void $this->files->put($this->dnsmasqMasterConfigFile, $contents); // remove old ~/.config/valet/dnsmasq.conf file because things are moved to the ~/.config/valet/dnsmasq.d/ folder now - if (file_exists($file = dirname($this->dnsmasqUserConfigDir()).'/dnsmasq.conf')) { + if (file_exists($file = dirname($this->dnsmasqUserConfigDir()) . '/dnsmasq.conf')) { unlink($file); } @@ -96,9 +98,9 @@ public function ensureUsingDnsmasqDForConfigs(): void $contents = $this->files->getStub('etc-dnsmasq-valet.conf'); $contents = str_replace('VALET_HOME_PATH', VALET_HOME_PATH, $contents); $this->files->ensureDirExists($this->dnsmasqSystemConfDir, user()); - $this->files->putAsUser($this->dnsmasqSystemConfDir.'/dnsmasq-valet.conf', $contents); + $this->files->putAsUser($this->dnsmasqSystemConfDir . '/dnsmasq-valet.conf', $contents); - $this->files->ensureDirExists(VALET_HOME_PATH.'/dnsmasq.d', user()); + $this->files->ensureDirExists(VALET_HOME_PATH . '/dnsmasq.d', user()); } /** @@ -112,6 +114,38 @@ public function createDnsmasqTldConfigFile(string $tld): void $this->files->putAsUser($tldConfigFile, 'address=/.'.$tld.'/'.$loopback.PHP_EOL.'listen-address='.$loopback.PHP_EOL); } + /** + * Create host-specific dnsmasq config (address=/fqdn/loopback). + */ + public function createHostConfig(string $fqdn): void + { + $dir = $this->dnsmasqUserConfigDir(); + $this->files->ensureDirExists($dir, user()); + $loopback = $this->configuration->read()['loopback']; + $file = $dir . 'host-' . $fqdn . '.conf'; + $contents = 'address=/' . $fqdn . '/' . $loopback . PHP_EOL . 'listen-address=' . $loopback . PHP_EOL; + $this->files->putAsUser($file, $contents); + } + + /** + * Remove host-specific dnsmasq config. + */ + public function deleteHostConfig(string $fqdn): void + { + $file = $this->dnsmasqUserConfigDir() . 'host-' . $fqdn . '.conf'; + if ($this->files->exists($file)) { + $this->files->unlink($file); + } + } + + /** + * Restart dnsmasq (after changes to *.conf). + */ + public function reload(): void + { + $this->brew->restartService('dnsmasq'); + } + /** * Create the resolver file to point the configured TLD to configured loopback address. */ @@ -120,7 +154,7 @@ public function createTldResolver(string $tld): void $this->files->ensureDirExists($this->resolverPath); $loopback = $this->configuration->read()['loopback']; - $this->files->put($this->resolverPath.'/'.$tld, 'nameserver '.$loopback.PHP_EOL); + $this->files->put($this->resolverPath . '/' . $tld, 'nameserver ' . $loopback . PHP_EOL); } /** @@ -131,7 +165,7 @@ public function updateTld(string $oldTld, string $newTld): void $this->files->unlink($this->resolverPath.'/'.$oldTld); $this->files->unlink($this->dnsmasqUserConfigDir().'tld-'.$oldTld.'.conf'); - $this->install($newTld); + $this->reload(); } /** @@ -149,6 +183,45 @@ public function refreshConfiguration(): void */ public function dnsmasqUserConfigDir(): string { - return $_SERVER['HOME'].'/.config/valet/dnsmasq.d/'; + return $_SERVER['HOME'] . '/.config/valet/dnsmasq.d/'; + } + + /** + * Return all existing host entries for a given base name (without TLD), + * as "." strings, e.g. ["example.dev", "example.test"]. + * + * It looks for files matching: host-.*.conf in the dnsmasq user config dir. + * + * @param string $name Base host name without TLD (e.g. "example") + * @return array List of "." strings (unique, naturally sorted) + */ + public function listSitesWithTld(string $name): array + { + // Normalize to lowercase to match how Valet typically writes FQDNs + $base = strtolower($name); + + // Ensure we have a trailing slash + $dir = rtrim($this->dnsmasqUserConfigDir(), '/').'/'; + + // Pattern for files like: host-example.dev.conf, host-example.test.conf, ... + $pattern = $dir . 'host-' . $base . '.*.conf'; + + // glob() returns an array of paths or an empty array if none + $matches = glob($pattern, GLOB_NOSORT) ?: []; + + $out = []; + foreach ($matches as $path) { + $filename = basename($path); // e.g. "host-example.dev.conf" + + // Extract between "host-" and ".conf" -> "example.dev" + if (preg_match('/^host-(.+)\.conf$/i', $filename, $m)) { + $out[] = $m[1]; + } + } + + // Ensure uniqueness and natural case-insensitive sort for stable output + $out = array_values(array_unique($out, SORT_STRING)); + natcasesort($out); + return array_values($out); } } diff --git a/cli/Valet/Site.php b/cli/Valet/Site.php index 86ba1d4b4..cf3c0f515 100644 --- a/cli/Valet/Site.php +++ b/cli/Valet/Site.php @@ -9,14 +9,25 @@ class Site { - public function __construct(public Brew $brew, public Configuration $config, public CommandLine $cli, public Filesystem $files) {} + protected DnsMasq $dnsmasq; + + public function __construct( + public Brew $brew, + public Configuration $config, + public CommandLine $cli, + public Filesystem $files + ) + { + // Lazy-Init: own DnsMasq instance, compatible with existing cabling + $this->dnsmasq = new DnsMasq($this->brew, $this->cli, $this->files, $this->config); + } /** * Get the name of the site. */ private function getSiteLinkName(?string $name): string { - if (! is_null($name)) { + if (!is_null($name)) { return $name; } @@ -24,7 +35,7 @@ private function getSiteLinkName(?string $name): string return $link; } - throw new DomainException(basename(getcwd()).' is not linked.'); + throw new DomainException(basename(getcwd()) . ' is not linked.'); } /** @@ -62,7 +73,7 @@ public function host(string $path): ?string /** * Link the current working directory with the given name. */ - public function link(string $target, string $link): string + public function link(string $target, string $link, string|null $tld = null): string { $this->files->ensureDirExists( $linkPath = $this->sitesPath(), user() @@ -70,9 +81,20 @@ public function link(string $target, string $link): string $this->config->prependPath($linkPath); - $this->files->symlinkAsUser($target, $linkPath.'/'.$link); + $this->files->symlinkAsUser($target, $linkPath . '/' . $link); + + if($tld && $tld !== $this->config->read()['tld']) { + + $sites = $this->dnsmasq->listSitesWithTld($link); - return $linkPath.'/'.$link; + foreach($sites as $site) { + $this->dnsmasq->deleteHostConfig($site); + } + + $this->dnsmasq->createHostConfig($link . '.' . $tld); + $this->dnsmasq->reload(); + } + return $linkPath . '/' . $link; } /** @@ -107,7 +129,7 @@ public function parked(): Collection // Only merge on the parked sites that don't interfere with the linked sites $sites = $this->getSites($path, $certs)->filter(function ($site, $key) use ($links) { - return ! $links->has($key); + return !$links->has($key); }); $parkedLinks = $parkedLinks->merge($sites); @@ -126,17 +148,17 @@ public function proxies(): Collection $links = $this->links(); $certs = $this->getCertificates(); - if (! $this->files->exists($dir)) { + if (!$this->files->exists($dir)) { return collect(); } $proxies = collect($this->files->scandir($dir)) ->filter(function ($site, $key) use ($tld) { // keep sites that match our TLD - return ends_with($site, '.'.$tld); + return ends_with($site, '.' . $tld); })->map(function ($site, $key) use ($tld) { // remove the TLD suffix for consistency - return str_replace('.'.$tld, '', $site); + return str_replace('.' . $tld, '', $site); })->reject(function ($site, $key) use ($links) { return $links->has($site); })->mapWithKeys(function ($site) { @@ -148,7 +170,7 @@ public function proxies(): Collection return $host === '(other)'; })->map(function ($host, $site) use ($certs, $tld) { $secured = $certs->has($site); - $url = ($secured ? 'https' : 'http').'://'.$site.'.'.$tld; + $url = ($secured ? 'https' : 'http') . '://' . $site . '.' . $tld; return [ 'site' => $site, @@ -173,15 +195,15 @@ public function getSiteUrl(string $directory): string } // Remove .tld from the end of sitename if it was provided - if (ends_with($directory, '.'.$tld)) { - $directory = substr($directory, 0, -(strlen('.'.$tld))); + if (ends_with($directory, '.' . $tld)) { + $directory = substr($directory, 0, -(strlen('.' . $tld))); } - if (! $this->parked()->merge($this->links())->where('site', $directory)->count() > 0) { + if (!$this->parked()->merge($this->links())->where('site', $directory)->count() > 0) { throw new DomainException("The [{$directory}] site could not be found in Valet's site list."); } - return $directory.'.'.$tld; + return $directory . '.' . $tld; } /** @@ -209,8 +231,8 @@ public function getProxyHostForSite(string $site, ?string $configContents = null public function getSiteConfigFileContents(string $site, ?string $suffix = null): ?string { $config = $this->config->read(); - $suffix = $suffix ?: '.'.$config['tld']; - $file = str_replace($suffix, '', $site).$suffix; + $suffix = $suffix ?: '.' . $config['tld']; + $file = str_replace($suffix, '', $site) . $suffix; return $this->files->exists($this->nginxPath($file)) ? $this->files->get($this->nginxPath($file)) : null; } @@ -253,7 +275,7 @@ public function getSites(string $path, Collection $certs): Collection $this->files->ensureDirExists($path, user()); return collect($this->files->scandir($path))->mapWithKeys(function ($site) use ($path) { - $sitePath = $path.'/'.$site; + $sitePath = $path . '/' . $site; if ($this->files->isLink($sitePath)) { $realPath = $this->files->readLink($sitePath); @@ -266,8 +288,8 @@ public function getSites(string $path, Collection $certs): Collection return $this->files->isDir($path); })->map(function ($path, $site) use ($certs, $config) { $secured = $certs->has($site); - $url = ($secured ? 'https' : 'http').'://'.$site.'.'.$config['tld']; - $phpVersion = $this->getPhpVersion($site.'.'.$config['tld']); + $url = ($secured ? 'https' : 'http') . '://' . $site . '.' . $config['tld']; + $phpVersion = $this->getPhpVersion($site . '.' . $config['tld']); return [ 'site' => $site, @@ -290,6 +312,12 @@ public function unlink(?string $name = null): string $this->files->unlink($path); } + $sites = $this->dnsmasq->listSitesWithTld($name);; + foreach($sites as $site) { + $this->dnsmasq->deleteHostConfig($site); + } + $this->dnsmasq->reload(); + return $name; } @@ -298,7 +326,7 @@ public function unlink(?string $name = null): string */ public function pruneLinks(): void { - if (! $this->files->isDir(VALET_HOME_PATH)) { + if (!$this->files->isDir(VALET_HOME_PATH)) { return; } @@ -330,25 +358,25 @@ public function getPhpVersion(string $url): string */ public function resecureForNewConfiguration(array $old, array $new): void { - if (! $this->files->exists($this->certificatesPath())) { + if (!$this->files->exists($this->certificatesPath())) { return; } $secured = $this->secured(); $defaultTld = $this->config->read()['tld']; - $oldTld = ! empty($old['tld']) ? $old['tld'] : $defaultTld; - $tld = ! empty($new['tld']) ? $new['tld'] : $defaultTld; + $oldTld = !empty($old['tld']) ? $old['tld'] : $defaultTld; + $tld = !empty($new['tld']) ? $new['tld'] : $defaultTld; $defaultLoopback = $this->config->read()['loopback']; - $oldLoopback = ! empty($old['loopback']) ? $old['loopback'] : $defaultLoopback; - $loopback = ! empty($new['loopback']) ? $new['loopback'] : $defaultLoopback; + $oldLoopback = !empty($old['loopback']) ? $old['loopback'] : $defaultLoopback; + $loopback = !empty($new['loopback']) ? $new['loopback'] : $defaultLoopback; foreach ($secured as $url) { - $newUrl = str_replace('.'.$oldTld, '.'.$tld, $url); - $siteConf = $this->getSiteConfigFileContents($url, '.'.$oldTld); + $newUrl = str_replace('.' . $oldTld, '.' . $tld, $url); + $siteConf = $this->getSiteConfigFileContents($url, '.' . $oldTld); - if (! empty($siteConf) && strpos($siteConf, '# valet stub: secure.proxy.valet.conf') === 0) { + if (!empty($siteConf) && strpos($siteConf, '# valet stub: secure.proxy.valet.conf') === 0) { // proxy config $this->unsecure($url); $this->secure( @@ -407,10 +435,10 @@ public function replaceOldLoopbackWithNew(string $siteConf, string $old, string $replaced = str_replace($old, $new, $match); if ($shouldComment && strpos($replaced, '#') !== 0) { - $replaced = '#'.$replaced; + $replaced = '#' . $replaced; } - if (! $shouldComment) { + if (!$shouldComment) { $replaced = ltrim($replaced, '#'); } @@ -440,7 +468,7 @@ public function secured(): array public function securedWithDates($ca = false): array { $sites = collect($this->secured())->map(function ($site) { - $filePath = $this->certificatesPath().'/'.$site.'.crt'; + $filePath = $this->certificatesPath() . '/' . $site . '.crt'; $expiration = $this->cli->run("openssl x509 -enddate -noout -in $filePath"); @@ -472,16 +500,16 @@ public function isSecured(string $site): bool { $tld = $this->config->read()['tld']; - return in_array($site.'.'.$tld, $this->secured()); + return in_array($site . '.' . $tld, $this->secured()); } /** * Secure the given host with TLS. * - * @param string|null $siteConf pregenerated Nginx config file contents - * @param int $certificateExpireInDays The number of days the self signed certificate is valid. + * @param string|null $siteConf pregenerated Nginx config file contents + * @param int $certificateExpireInDays The number of days the self signed certificate is valid. * Certificates SHOULD NOT have a validity period greater than 397 days. - * @param int $caExpireInYears The number of years the self signed certificate authority is valid. + * @param int $caExpireInYears The number of years the self signed certificate authority is valid. * * @see https://github.com/cabforum/servercert/blob/main/docs/BR.md */ @@ -527,14 +555,14 @@ public function renew($expireIn = 368, $ca = false): void $this->secure($url, null, $expireIn); - info('The ['.$url.'] site has been secured with a fresh TLS certificate.'); + info('The [' . $url . '] site has been secured with a fresh TLS certificate.'); }); } /** * If CA and root certificates are nonexistent, create them and trust the root cert. * - * @param int $caExpireInDays The number of days the self signed certificate authority is valid. + * @param int $caExpireInDays The number of days the self signed certificate authority is valid. */ public function createCa(int $caExpireInDays): void { @@ -582,7 +610,7 @@ public function createCa(int $caExpireInDays): void public function removeCa(): void { foreach (['pem', 'key', 'srl'] as $ending) { - $path = $this->caPath('LaravelValetCASelfSigned.'.$ending); + $path = $this->caPath('LaravelValetCASelfSigned.' . $ending); if ($this->files->exists($path)) { $this->files->unlink($path); @@ -600,7 +628,7 @@ public function removeCa(): void /** * Create and trust a certificate for the given URL. * - * @param int $caExpireInDays The number of days the self signed certificate is valid. + * @param int $caExpireInDays The number of days the self signed certificate is valid. */ public function createCertificate(string $url, int $caExpireInDays): void { @@ -616,8 +644,8 @@ public function createCertificate(string $url, int $caExpireInDays): void $this->createPrivateKey($keyPath); $this->createSigningRequest($url, $keyPath, $csrPath); - $caSrlParam = '-CAserial "'.$caSrlPath.'"'; - if (! $this->files->exists($caSrlPath)) { + $caSrlParam = '-CAserial "' . $caSrlPath . '"'; + if (!$this->files->exists($caSrlPath)) { $caSrlParam .= ' -CAcreateserial'; } @@ -807,7 +835,7 @@ public function unsecureAll(): void table(['Site', 'SSL', 'URL', 'Path'], $secured->toArray()); foreach ($secured->pluck('site') as $url) { - $this->unsecure($url.'.'.$tld); + $this->unsecure($url . '.' . $tld); } $remaining = $this->parked() @@ -824,20 +852,20 @@ public function unsecureAll(): void /** * Build the Nginx proxy config for the specified domain. * - * @param string $url The domain name to serve - * @param string $host The URL to proxy to, eg: http://127.0.0.1:8080 + * @param string $url The domain name to serve + * @param string $host The URL to proxy to, eg: http://127.0.0.1:8080 */ - public function proxyCreate(string $url, string $host, bool $secure = false): void + public function proxyCreate(string $url, string $host, bool $secure = false, string|null $tld = null): void { - if (! preg_match('~^https?://.*$~', $host)) { + if (!preg_match('~^https?://.*$~', $host)) { throw new \InvalidArgumentException(sprintf('"%s" is not a valid URL', $host)); } - $tld = $this->config->read()['tld']; + $tld = $tld ?? $this->config->read()['tld']; foreach (explode(',', $url) as $proxyUrl) { - if (! ends_with($proxyUrl, '.'.$tld)) { - $proxyUrl .= '.'.$tld; + if (!ends_with($proxyUrl, '.' . $tld)) { + $proxyUrl .= '.' . $tld; } $nginxVersion = str_replace('nginx version: nginx/', '', exec('nginx -v 2>&1')); @@ -861,10 +889,21 @@ public function proxyCreate(string $url, string $host, bool $secure = false): vo $this->put($proxyUrl, $siteConf); } + if($tld && $tld !== $this->config->read()['tld']) { + + $sites = $this->dnsmasq->listSitesWithTld(str_replace('.'.$tld, '', $proxyUrl));; + foreach($sites as $site) { + $this->dnsmasq->deleteHostConfig($site); + } + + $this->dnsmasq->createHostConfig($proxyUrl); + } + $protocol = $secure ? 'https' : 'http'; - info('Valet will now proxy ['.$protocol.'://'.$proxyUrl.'] traffic to ['.$host.'].'); + info('Valet will now proxy [' . $protocol . '://' . $proxyUrl . '] traffic to [' . $host . '].'); } + $this->dnsmasq->reload(); } /** @@ -875,15 +914,18 @@ public function proxyDelete(string $url): void $tld = $this->config->read()['tld']; foreach (explode(',', $url) as $proxyUrl) { - if (! ends_with($proxyUrl, '.'.$tld)) { - $proxyUrl .= '.'.$tld; + if (!ends_with($proxyUrl, '.' . $tld)) { + $proxyUrl .= '.' . $tld; } $this->unsecure($proxyUrl); $this->files->unlink($this->nginxPath($proxyUrl)); - info('Valet will no longer proxy [https://'.$proxyUrl.'].'); + $this->dnsmasq->deleteHostConfig($proxyUrl); + + info('Valet will no longer proxy [https://' . $proxyUrl . '].'); } + $this->dnsmasq->reload(); } /** @@ -925,7 +967,7 @@ public function removeLoopbackAlias(string $loopback): void 'sudo ifconfig lo0 -alias %s', $loopback )); - info('['.$loopback.'] loopback interface alias removed.'); + info('[' . $loopback . '] loopback interface alias removed.'); } /** @@ -937,7 +979,7 @@ public function addLoopbackAlias(string $loopback): void 'sudo ifconfig lo0 alias %s', $loopback )); - info('['.$loopback.'] loopback interface alias added.'); + info('[' . $loopback . '] loopback interface alias added.'); } /** @@ -957,7 +999,7 @@ public function updateLoopbackPlist(string $loopback): void ) ); - info('['.$this->plistPath().'] persistent loopback interface alias launch daemon added.'); + info('[' . $this->plistPath() . '] persistent loopback interface alias launch daemon added.'); } } @@ -969,7 +1011,7 @@ public function removeLoopbackPlist(): void if ($this->files->exists($this->plistPath())) { $this->files->unlink($this->plistPath()); - info('['.$this->plistPath().'] persistent loopback interface alias launch daemon removed.'); + info('[' . $this->plistPath() . '] persistent loopback interface alias launch daemon removed.'); } } @@ -1014,7 +1056,7 @@ public function plistPath(): string */ public function nginxPath(?string $additionalPath = null): string { - return $this->valetHomePath().'/Nginx'.($additionalPath ? '/'.$additionalPath : ''); + return $this->valetHomePath() . '/Nginx' . ($additionalPath ? '/' . $additionalPath : ''); } /** @@ -1022,7 +1064,7 @@ public function nginxPath(?string $additionalPath = null): string */ public function sitesPath(?string $link = null): string { - return $this->valetHomePath().'/Sites'.($link ? '/'.$link : ''); + return $this->valetHomePath() . '/Sites' . ($link ? '/' . $link : ''); } /** @@ -1030,7 +1072,7 @@ public function sitesPath(?string $link = null): string */ public function caPath(?string $caFile = null): string { - return $this->valetHomePath().'/CA'.($caFile ? '/'.$caFile : ''); + return $this->valetHomePath() . '/CA' . ($caFile ? '/' . $caFile : ''); } /** @@ -1038,10 +1080,10 @@ public function caPath(?string $caFile = null): string */ public function certificatesPath(?string $url = null, ?string $extension = null): string { - $url = $url ? '/'.$url : ''; - $extension = $extension ? '.'.$extension : ''; + $url = $url ? '/' . $url : ''; + $extension = $extension ? '.' . $extension : ''; - return $this->valetHomePath().'/Certificates'.$url.$extension; + return $this->valetHomePath() . '/Certificates' . $url . $extension; } /** @@ -1058,12 +1100,12 @@ public function domain(?string $domain): string // } // Don't add .TLD if user already passed the string in with the TLD on the end - if ($domain && str_contains($domain, '.'.$this->config->read()['tld'])) { + if ($domain && str_contains($domain, '.' . $this->config->read()['tld'])) { return $domain; } // Return either the passed domain, or the current folder name, with .TLD appended - return ($domain ?: $this->host(getcwd())).'.'.$this->config->read()['tld']; + return ($domain ?: $this->host(getcwd())) . '.' . $this->config->read()['tld']; } /** @@ -1094,7 +1136,7 @@ public function customPhpVersion(string $url): ?string if ($this->files->exists($this->nginxPath($url))) { $siteConf = $this->files->get($this->nginxPath($url)); - if (starts_with($siteConf, '# '.ISOLATED_PHP_VERSION)) { + if (starts_with($siteConf, '# ' . ISOLATED_PHP_VERSION)) { $firstLine = explode(PHP_EOL, $siteConf)[0]; return preg_replace("/[^\d]*/", '', $firstLine); // Example output: "74" or "81" @@ -1112,9 +1154,9 @@ public function replaceSockFile(string $siteConf, string $phpVersion): string $sockFile = PhpFpm::fpmSockName($phpVersion); $siteConf = preg_replace('/valet[0-9]*.sock/', $sockFile, $siteConf); - $siteConf = preg_replace('/# '.ISOLATED_PHP_VERSION.'.*\n/', '', $siteConf); // Remove ISOLATED_PHP_VERSION line from config + $siteConf = preg_replace('/# ' . ISOLATED_PHP_VERSION . '.*\n/', '', $siteConf); // Remove ISOLATED_PHP_VERSION line from config - return '# '.ISOLATED_PHP_VERSION.'='.$phpVersion.PHP_EOL.$siteConf; + return '# ' . ISOLATED_PHP_VERSION . '=' . $phpVersion . PHP_EOL . $siteConf; } /** @@ -1123,9 +1165,9 @@ public function replaceSockFile(string $siteConf, string $phpVersion): string public function valetRc(string $siteName, ?string $cwd = null): array { if ($cwd) { - $path = $cwd.'/.valetrc'; + $path = $cwd . '/.valetrc'; } elseif ($site = $this->parked()->merge($this->links())->where('site', $siteName)->first()) { - $path = data_get($site, 'path').'/.valetrc'; + $path = data_get($site, 'path') . '/.valetrc'; } else { return []; } @@ -1149,9 +1191,9 @@ public function valetRc(string $siteName, ?string $cwd = null): array public function phpRcVersion(string $siteName, ?string $cwd = null): ?string { if ($cwd) { - $oldPath = $cwd.'/.valetphprc'; + $oldPath = $cwd . '/.valetphprc'; } elseif ($site = $this->parked()->merge($this->links())->where('site', $siteName)->first()) { - $oldPath = data_get($site, 'path').'/.valetphprc'; + $oldPath = data_get($site, 'path') . '/.valetphprc'; } else { return null; } diff --git a/cli/app.php b/cli/app.php index a5d273a49..9e274192e 100644 --- a/cli/app.php +++ b/cli/app.php @@ -198,8 +198,8 @@ function (ConsoleCommandEvent $event) { /** * Register a symbolic link with Valet. */ - $app->command('link [name] [--secure] [--isolate]', function ($name, $secure, $isolate) { - $linkPath = Site::link(getcwd(), $name = $name ?: basename(getcwd())); + $app->command('link [name] [--secure] [--isolate] [--tld=]', function ($name, $secure, $isolate, $tld) { + $linkPath = Site::link(getcwd(), $name = $name ?: basename(getcwd()), $tld); info('A ['.$name.'] symbolic link has been created in ['.$linkPath.'].'); @@ -318,8 +318,8 @@ function (ConsoleCommandEvent $event) { /** * Create an Nginx proxy config for the specified domain. */ - $app->command('proxy domain host [--secure]', function (OutputInterface $output, $domain, $host, $secure) { - Site::proxyCreate($domain, $host, $secure); + $app->command('proxy domain host [--secure] [--tld=]', function (OutputInterface $output, $domain, $host, $secure, $tld) { + Site::proxyCreate($domain, $host, $secure, $tld); Nginx::restart(); })->descriptions('Create an Nginx proxy site for the specified host. Useful for docker, mailhog etc.', [ '--secure' => 'Create a proxy with a trusted TLS certificate', diff --git a/tests/CliTest.php b/tests/CliTest.php index e231c5a30..6c76f1e47 100644 --- a/tests/CliTest.php +++ b/tests/CliTest.php @@ -228,7 +228,7 @@ public function test_link_command_defaults_to_cwd() [$app, $tester] = $this->appAndTester(); $site = Mockery::mock(RealSite::class); - $site->shouldReceive('link')->with(getcwd(), basename(getcwd()))->once(); + $site->shouldReceive('link')->with(getcwd(), basename(getcwd()), null)->once(); swap(RealSite::class, $site); $tester->run(['command' => 'link']); @@ -294,7 +294,7 @@ public function test_link_command_with_isolate_flag_isolates() $brewMock->shouldReceive('installed')->with($fullPhpVersion); $brewMock->shouldReceive('determineAliasedVersion')->with($fullPhpVersion)->andReturn($fullPhpVersion); - $siteMock->shouldReceive('link')->with($cwd, $name)->once(); + $siteMock->shouldReceive('link')->with($cwd, $name, null)->once(); $siteMock->shouldReceive('getSiteUrl')->with($name)->andReturn($host); $siteMock->shouldReceive('phpRcVersion')->with($name, $cwd)->andReturn($phpRcVersion); $siteMock->shouldReceive('customPhpVersion')->with($host)->andReturn($customPhpVersion); @@ -471,7 +471,7 @@ public function test_proxy_command() [$app, $tester] = $this->appAndTester(); $site = Mockery::mock(RealSite::class); - $site->shouldReceive('proxyCreate')->with('elasticsearch', 'http://127.0.0.1:9200', false)->once(); + $site->shouldReceive('proxyCreate')->with('elasticsearch', 'http://127.0.0.1:9200', false, null)->once(); $nginx = Mockery::mock(Nginx::class); $nginx->shouldReceive('restart')->once(); @@ -488,7 +488,7 @@ public function test_proxy_command_with_multiple_domains() [$app, $tester] = $this->appAndTester(); $site = Mockery::mock(RealSite::class); - $site->shouldReceive('proxyCreate')->with('my-app,subdomain.my-app', 'http://127.0.0.1:8000', false)->once(); + $site->shouldReceive('proxyCreate')->with('my-app,subdomain.my-app', 'http://127.0.0.1:8000', false, null)->once(); $nginx = Mockery::mock(Nginx::class); $nginx->shouldReceive('restart')->once();