diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 12b4bc8..6a55133 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,17 +1,16 @@
-name: CI-Ubuntu
+name: CI
on: [pull_request]
jobs:
- build:
- name: Build & Test on Ubuntu 22.04
+ test:
+ name: Build & Test
runs-on: ubuntu-22.04
- if: github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master'
strategy:
matrix:
php:
- - 8.0
- - 8.1
+ - 8.2
+ - 8.3
steps:
- uses: actions/checkout@v2
- name: Set up PHP
@@ -25,3 +24,23 @@ jobs:
composer install --no-interaction --prefer-dist
- name: Test
run: vendor/bin/phpunit
+ cs_fixer:
+ name: CS Fixer
+ runs-on: ubuntu-22.04
+ strategy:
+ matrix:
+ php:
+ - 8.3
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y network-manager libnss3-tools jq xsel
+ composer install --no-interaction --prefer-dist
+ - name: CS Fixer
+ run: vendor/bin/php-cs-fixer check --config=.php-cs-fixer.dist.php
diff --git a/.gitignore b/.gitignore
index e60bf7f..ef1889c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,10 @@
vendor/
+bin/
+tests/config
composer.lock
error.log
*.sublime-project
*.sublime-workspace
-psysh
-Gemfile
-Gemfile.lock
-_site/
.idea
-/.php-cs-fixer.cache
+.php-cs-fixer.cache
+.phpunit.result.cache
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..3df7873
--- /dev/null
+++ b/.php-cs-fixer.dist.php
@@ -0,0 +1,56 @@
+in([
+ __DIR__.'/cli/',
+ __DIR__.'/tests/'
+ ]);
+
+$config = new PhpCsFixer\Config();
+return $config
+ ->setRiskyAllowed(true)
+ ->setUsingCache(false)
+ ->setRules([
+ '@PSR12' => true,
+ 'list_syntax' => ['syntax' => 'short'],
+ 'multiline_comment_opening_closing' => true,
+ 'no_blank_lines_after_phpdoc' => true,
+ 'no_empty_comment' => true,
+ 'no_empty_phpdoc' => true,
+ 'no_empty_statement' => true,
+ 'no_extra_blank_lines' => true,
+ 'no_spaces_after_function_name' => true,
+ 'no_superfluous_elseif' => true,
+ 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true],
+ 'no_unused_imports' => true,
+ 'no_useless_else' => true,
+ 'no_whitespace_before_comma_in_array' => true,
+ 'no_whitespace_in_blank_line' => true,
+ 'object_operator_without_whitespace' => true,
+ 'ordered_imports' => ['sort_algorithm' => 'alpha', 'imports_order' => ['class', 'function', 'const']],
+ 'php_unit_construct' => true,
+ 'php_unit_expectation' => true,
+ 'php_unit_mock' => ['target' => 'newest'],
+ 'php_unit_mock_short_will_return' => true,
+ 'php_unit_no_expectation_annotation' => true,
+ 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'],
+ 'phpdoc_add_missing_param_annotation' => true,
+ 'phpdoc_line_span' => ['method' => 'multi', 'property' => 'multi'],
+ 'phpdoc_no_useless_inheritdoc' => true,
+ 'phpdoc_scalar' => true,
+ 'phpdoc_single_line_var_spacing' => true,
+ 'phpdoc_trim' => true,
+ 'phpdoc_trim_consecutive_blank_line_separation' => true,
+ 'phpdoc_types' => true,
+ 'phpdoc_var_annotation_correct_order' => true,
+ 'phpdoc_var_without_name' => true,
+ 'protected_to_private' => true,
+ 'return_assignment' => true,
+ 'short_scalar_cast' => true,
+ 'single_trait_insert_per_statement' => true,
+ 'standardize_not_equals' => true,
+ 'trim_array_spaces' => true,
+ 'visibility_required' => ['elements' => ['const']],
+ 'whitespace_after_comma_in_array' => true,
+ ])
+ ->setFinder($finder);
diff --git a/README.md b/README.md
index 307636c..8d0c794 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
-
diff --git a/TODO b/TODO
index 420f6e1..8bc1a3f 100644
--- a/TODO
+++ b/TODO
@@ -1,13 +1,27 @@
-1. Finish refactoring classes in Valet directory -- Done
-2. Fix facade.php -- Done
-3. Move config directory from ~/.valet to ~/.config/valet
- 3.1 Copy directory from ~/.valet to ~/.config
- 3.2 Replace /home/uttam/.valet path with /home/uttam/.config/valet for Nginx directory files (Logs & Certificate, .sock files)
- 3.3 Replace .sock file paths in valet.conf file.
- 3.4 Regenerate symbolic links in Sites directory
-4. Add relevant test cases
-5. Enable Phpstan & CodeSniffer -- Done
-6. Enable unit coverage report
-7. Add Infection
-8. Ngrok remove binary file and install it via `valet install` command
-9. self-signed certificate verification issues in Firefox & PHP's curl requests
+Remain Items:
+- Enable unit coverage report
+- Add Infection
+- Valet php extension install separately so that if not installed we can install it via valet install command. (Optional)
+- Improve valet uninstall experience
+ - User should be able to select which services they want to uninstall
+- mbstring extension was not installed in system, but composer install command fail to stop process as it was mentioned in required json
+
+
+Done Items:
+- Implement termwind -- Done
+- Fix extra messages (Fixing, already enabled, restarting) -- Done
+ - Remove was already enabled message -- Done
+- Enable Phpstan & CodeSniffer -- Done
+- Finish refactoring classes in Valet directory -- Done
+- Fix facade.php -- Done
+- Move config directory from ~/.valet to ~/.config/valet -- Done
+- Update valet use command to only work with current supported PHP versions
+- Update valet isolate command to work with every php version from 7.0 to latest
+- Optimise php bash file to handle blank output of which-php command
+- self-signed certificate verification issues in Firefox & PHP's curl requests -- Done
+- Ngrok remove binary file and install it via `valet install` command
+- Optimise PHP Bash file to not relay on which-php command
+ - It should read fallback version from ~/.config/valet/config.json file
+ - It should read isolated version for directory from ~/.config/valet/config.json file
+- Add relevant test cases
+ -
diff --git a/cli/Valet/CommandLine.php b/cli/Valet/CommandLine.php
index bfcb843..56b0edb 100644
--- a/cli/Valet/CommandLine.php
+++ b/cli/Valet/CommandLine.php
@@ -48,21 +48,13 @@ public function runAsUser(string $command, callable $onError = null): string
/**
* Run the given command.
- * TODO: Refactor new Process instance, we might not need if statement there.
*/
private function runCommand(string $command, callable $onError = null): string
{
- $onError = $onError ?: function () {};
+ $onError = $onError ?: function () {
+ };
- // Symfony's 4.x Process component has deprecated passing a command string
- // to the constructor, but older versions (which Valet's Composer
- // constraints allow) don't have the fromShellCommandLine method.
- // For more information, see: https://github.com/laravel/valet/pull/761
- if (method_exists(Process::class, 'fromShellCommandline')) {
- $process = Process::fromShellCommandline($command);
- } else {
- $process = new Process($command);
- }
+ $process = Process::fromShellCommandline($command);
$processOutput = '';
$process->setTimeout(null)->run(function ($type, $line) use (&$processOutput) {
diff --git a/cli/Valet/Configuration.php b/cli/Valet/Configuration.php
index d0b0a67..be32570 100644
--- a/cli/Valet/Configuration.php
+++ b/cli/Valet/Configuration.php
@@ -6,7 +6,7 @@
class Configuration
{
- public $files;
+ public Filesystem $files;
/**
* Create a new Valet configuration class instance.
@@ -43,80 +43,6 @@ public function uninstall(): void
}
}
- /**
- * Create the Valet configuration directory.
- */
- public function createConfigurationDirectory(): void
- {
- $this->files->ensureDirExists(VALET_HOME_PATH, user());
- }
-
- /**
- * Create the Valet drivers directory.
- */
- public function createDriversDirectory(): void
- {
- if ($this->files->isDir($driversDirectory = VALET_HOME_PATH.'/Drivers')) {
- return;
- }
-
- $this->files->mkdirAsUser($driversDirectory);
-
- $this->files->putAsUser(
- $driversDirectory.'/SampleValetDriver.php',
- $this->files->get(__DIR__.'/../stubs/SampleValetDriver.php')
- );
- }
-
- /**
- * Create the Valet sites directory.
- */
- public function createSitesDirectory(): void
- {
- $this->files->ensureDirExists(VALET_HOME_PATH.'/Sites', user());
- }
-
- /**
- * Create the directory for the Valet extensions.
- */
- public function createExtensionsDirectory(): void
- {
- $this->files->ensureDirExists(VALET_HOME_PATH.'/Extensions', user());
- }
-
- /**
- * Create the directory for Nginx logs.
- */
- public function createLogDirectory(): void
- {
- $this->files->ensureDirExists(VALET_HOME_PATH.'/Log', user());
-
- $this->files->touch(VALET_HOME_PATH.'/Log/nginx-error.log');
- }
-
- /**
- * Create the directory for SSL certificates.
- */
- public function createCertificatesDirectory(): void
- {
- $this->files->ensureDirExists(VALET_HOME_PATH.'/Certificates', user());
- }
-
- /**
- * Write the base, initial configuration for Valet.
- */
- public function writeBaseConfiguration(): void
- {
- if (!$this->files->exists($this->path())) {
- $this->write([
- 'domain' => 'test',
- 'paths' => [],
- 'port' => '80',
- 'installed_php_version' => PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION,
- ]);
- }
- }
-
/**
* Add the given path to the configuration.
*/
@@ -129,14 +55,6 @@ public function addPath(string $path, bool $prepend = false): void
}));
}
- /**
- * Prepend the given path to the configuration.
- */
- public function prependPath(string $path): void
- {
- $this->addPath($path, true);
- }
-
/**
* Add the given path to the configuration.
*/
@@ -165,14 +83,6 @@ public function prune(): void
}));
}
- /**
- * Read the configuration file as JSON.
- */
- public function read(): array
- {
- return json_decode($this->files->get($this->path()), true);
- }
-
/**
* Get a configuration value.
* @param mixed $default
@@ -195,13 +105,22 @@ public function set(string $key, $value)
return $this->updateKey($key, $value);
}
+ public function parseDomain(string $siteName): string
+ {
+ $domain = $this->get('domain');
+ if (str_ends_with($siteName, ".$domain") !== true) {
+ return \sprintf('%s.%s', $siteName, $domain);
+ }
+ return $siteName;
+ }
+
/**
* Update a specific key in the configuration file.
*
* @param mixed $value
* @return array
*/
- public function updateKey(string $key, $value)
+ private function updateKey(string $key, $value)
{
return tap($this->read(), function (&$config) use ($key, $value) {
$config[$key] = $value;
@@ -212,7 +131,7 @@ public function updateKey(string $key, $value)
/**
* Write the given configuration to disk.
*/
- public function write(array $config): void
+ private function write(array $config): void
{
$this->files->putAsUser(
$this->path(),
@@ -223,10 +142,91 @@ public function write(array $config): void
);
}
+ /**
+ * Read the configuration file as JSON.
+ */
+ private function read(): array
+ {
+ return json_decode($this->files->get($this->path()), true);
+ }
+
+ /**
+ * Create the Valet configuration directory.
+ */
+ private function createConfigurationDirectory(): void
+ {
+ $this->files->ensureDirExists(VALET_HOME_PATH, user());
+ }
+
+ /**
+ * Create the Valet drivers directory.
+ */
+ private function createDriversDirectory(): void
+ {
+ if ($this->files->isDir($driversDirectory = VALET_HOME_PATH.'/Drivers')) {
+ return;
+ }
+
+ $this->files->mkdirAsUser($driversDirectory);
+
+ $this->files->putAsUser(
+ $driversDirectory.'/SampleValetDriver.php',
+ $this->files->get(VALET_ROOT_PATH.'/cli/stubs/SampleValetDriver.php')
+ );
+ }
+
+ /**
+ * Create the Valet sites directory.
+ */
+ private function createSitesDirectory(): void
+ {
+ $this->files->ensureDirExists(VALET_HOME_PATH.'/Sites', user());
+ }
+
+ /**
+ * Create the directory for the Valet extensions.
+ */
+ private function createExtensionsDirectory(): void
+ {
+ $this->files->ensureDirExists(VALET_HOME_PATH.'/Extensions', user());
+ }
+
+ /**
+ * Create the directory for Nginx logs.
+ */
+ private function createLogDirectory(): void
+ {
+ $this->files->ensureDirExists(VALET_HOME_PATH.'/Log', user());
+
+ $this->files->touch(VALET_HOME_PATH.'/Log/nginx-error.log');
+ }
+
+ /**
+ * Create the directory for SSL certificates.
+ */
+ private function createCertificatesDirectory(): void
+ {
+ $this->files->ensureDirExists(VALET_HOME_PATH.'/Certificates', user());
+ }
+
+ /**
+ * Write the base, initial configuration for Valet.
+ */
+ private function writeBaseConfiguration(): void
+ {
+ if (!$this->files->exists($this->path())) {
+ $this->write([
+ 'domain' => 'test',
+ 'paths' => [],
+ 'port' => '80',
+ ]);
+ }
+ }
+
/**
* Get the configuration file path.
*/
- public function path(): string
+ private function path(): string
{
return VALET_HOME_PATH.'/config.json';
}
diff --git a/cli/Valet/Contracts/PackageManager.php b/cli/Valet/Contracts/PackageManager.php
index 3d2d10e..4223937 100644
--- a/cli/Valet/Contracts/PackageManager.php
+++ b/cli/Valet/Contracts/PackageManager.php
@@ -36,7 +36,6 @@ public function getPhpFpmName(string $version): string;
/**
* Get Php extension pattern from distro
- * TODO: This function is refactored, please update the usage.
*/
public function getPhpExtensionPrefix(string $version): string;
@@ -44,4 +43,9 @@ public function getPhpExtensionPrefix(string $version): string;
* Restart network manager in distro
*/
public function restartNetworkManager(): void;
+
+ /**
+ * Get package name by service
+ */
+ public function packageName(string $name): string;
}
diff --git a/cli/Valet/Contracts/ServiceManager.php b/cli/Valet/Contracts/ServiceManager.php
index 84b7751..2b88da6 100644
--- a/cli/Valet/Contracts/ServiceManager.php
+++ b/cli/Valet/Contracts/ServiceManager.php
@@ -7,23 +7,23 @@ interface ServiceManager
/**
* Start the given services.
*
- * @param array|string $services
+ * @param array|string|string[]|null $services
*/
- public function start($services): void;
+ public function start(array|string|null $services): void;
/**
* Stop the given services.
*
- * @param array|string $services
+ * @param array|string|string[]|null $services
*/
- public function stop($services): void;
+ public function stop(array|string|null $services): void;
/**
* Restart the given services.
*
- * @param array|string $services
+ * @param array|string|string[]|null $services
*/
- public function restart($services): void;
+ public function restart(array|string|null $services): void;
/**
* Enable the given services.
@@ -54,4 +54,9 @@ public function printStatus(string $service): void;
* If the service manager is systemd.
*/
public function isSystemd(): bool;
+
+ /**
+ * Remove Valet DNS services.
+ */
+ public function removeValetDns(): void;
}
diff --git a/cli/Valet/DevTools.php b/cli/Valet/DevTools.php
index 361b597..38c5d99 100644
--- a/cli/Valet/DevTools.php
+++ b/cli/Valet/DevTools.php
@@ -2,6 +2,7 @@
namespace Valet;
+use ConsoleComponents\Writer;
use DomainException;
use Valet\Contracts\PackageManager;
use Valet\Contracts\ServiceManager;
@@ -11,38 +12,27 @@ class DevTools
/**
* Sublime binary selector.\
*/
- const VS_CODE = 'code';
+ public const VS_CODE = 'code';
+
/**
* Sublime binary selector.
*/
- const SUBLIME = 'subl';
+ public const SUBLIME = 'subl';
/**
* PHPStorm binary selector.
*/
- const PHP_STORM = 'phpstorm.sh';
+ public const PHP_STORM = 'phpstorm.sh';
/**
* Atom binary selector.
*/
- const ATOM = 'atom';
+ public const ATOM = 'atom';
- /**
- * @var PackageManager
- */
- public $pm;
- /**
- * @var ServiceManager
- */
- public $sm;
- /**
- * @var CommandLine
- */
- public $cli;
- /**
- * @var Filesystem
- */
- public $files;
+ public PackageManager $pm;
+ public ServiceManager $sm;
+ public CommandLine $cli;
+ public Filesystem $files;
/**
* Create a new DevTools instance.
@@ -56,23 +46,41 @@ public function __construct(PackageManager $pm, ServiceManager $sm, CommandLine
}
/**
- * @return false|string
+ * @param string[] $ignoredServices
*/
- public function getBin(string $service)
+ public function getBin(string $service, array $ignoredServices = []): false|string
{
- if (!($bin = $this->getService($service))) {
- $bin = $this->getService($service, true);
+ $bin = $this->getService($service);
+
+ $bin = trim($bin, "\n");
+ if (count($ignoredServices) && in_array($bin, $ignoredServices)) {
+ $bin = null;
+ }
+
+ if (!$bin) {
+ $bin = $this->getServiceByLocate("bin/$service");
+ }
+
+ if (!$bin) {
+ return false;
}
+
+ $bin = trim($bin, "\n");
+ /** @var string[] $bins */
$bins = preg_split('/\n/', $bin);
$servicePath = null;
foreach ($bins as $bin) {
- if (endsWith($bin, "bin/${service}")) {
+ if ((count($ignoredServices) && !in_array($bin, $ignoredServices))
+ || !count($ignoredServices)
+ ) {
$servicePath = $bin;
break;
}
}
- if ($servicePath) {
- return trim(preg_replace('/\s\s+/', ' ', $servicePath));
+ if ($servicePath !== null) {
+ /** @var string $servicePath */
+ $servicePath = preg_replace('/\s\s+/', ' ', $servicePath);
+ return trim($servicePath);
}
return false;
@@ -80,10 +88,10 @@ public function getBin(string $service)
public function run(string $folder, string $service): void
{
- if ($this->ensureInstalled($service)) {
- $this->runService($service, $folder);
+ if ($bin = $this->ensureInstalled($service)) {
+ $this->runService($bin, $folder);
} else {
- warning("$service not available");
+ Writer::warn("$service not available");
}
}
@@ -98,13 +106,11 @@ private function ensureInstalled(string $service)
/**
* @return false|string
*/
- private function getService(string $service, bool $locate = false)
+ private function getService(string $service)
{
try {
- $locator = $locate ? 'locate' : 'which';
-
return $this->cli->run(
- "$locator $service",
+ "which $service",
function () {
throw new DomainException('Service not available');
}
@@ -114,14 +120,25 @@ function () {
}
}
- private function runService(string $service, ?string $folder = null): void
+ /**
+ * @return false|string
+ */
+ private function getServiceByLocate(string $service)
{
- $bin = $this->getBin($service);
-
try {
- $this->cli->quietly("$bin $folder");
+ return $this->cli->run(
+ "locate --regex $service$",
+ function () {
+ throw new DomainException('Service not available');
+ }
+ );
} catch (DomainException $e) {
- warning("Error while opening [$folder] with $service");
+ return false;
}
}
+
+ private function runService(string $bin, ?string $folder = null): void
+ {
+ $this->cli->quietly("$bin $folder");
+ }
}
diff --git a/cli/Valet/DnsMasq.php b/cli/Valet/DnsMasq.php
index d6dbad4..79f6831 100644
--- a/cli/Valet/DnsMasq.php
+++ b/cli/Valet/DnsMasq.php
@@ -2,56 +2,24 @@
namespace Valet;
+use ConsoleComponents\Writer;
use Exception;
use Valet\Contracts\PackageManager;
use Valet\Contracts\ServiceManager;
class DnsMasq
{
- /**
- * @var PackageManager
- */
- public $pm;
- /**
- * @var ServiceManager
- */
- public $sm;
- /**
- * @var CommandLine
- */
- public $cli;
- /**
- * @var Filesystem
- */
- public $files;
- /**
- * @var string
- */
- public $rclocal = '/etc/rc.local';
- /**
- * @var string
- */
- public $resolvconf = '/etc/resolv.conf';
- /**
- * @var string
- */
- public $dnsmasqconf = '/etc/dnsmasq.conf';
- /**
- * @var string
- */
- public $dnsmasqOpts = '/etc/dnsmasq.d/options';
- /**
- * @var string
- */
- public $resolvedConfigPath = '/etc/systemd/resolved.conf';
- /**
- * @var string
- */
- public $configPath = '/etc/dnsmasq.d/valet';
- /**
- * @var string
- */
- public $nmConfigPath = '/etc/NetworkManager/conf.d/valet.conf';
+ public PackageManager $pm;
+ public ServiceManager $sm;
+ public CommandLine $cli;
+ public Filesystem $files;
+ public string $rclocal = '/etc/rc.local';
+ public string $resolvconf = '/etc/resolv.conf';
+ public string $dnsmasqconf = '/etc/dnsmasq.conf';
+ public string $dnsmasqOpts = '/etc/dnsmasq.d/options';
+ public string $resolvedConfigPath = '/etc/systemd/resolved.conf';
+ public string $configPath = '/etc/dnsmasq.d/valet';
+ public string $nmConfigPath = '/etc/NetworkManager/conf.d/valet.conf';
/**
* Create a new DnsMasq instance.
@@ -69,7 +37,7 @@ public function __construct(PackageManager $pm, ServiceManager $sm, Filesystem $
*
* @throws Exception
*/
- public function install(string $domain = 'test'): void
+ public function install(string $domain): void
{
$this->dnsmasqSetup();
$this->stopResolved();
@@ -115,7 +83,7 @@ public function uninstall(): void
$this->files->unlink($this->nmConfigPath);
$this->files->restore($this->resolvedConfigPath);
- $this->lockResolvConf(false);
+ $this->lockResolvConf();
$this->files->restore($this->rclocal);
$this->cli->passthru('rm -f /etc/resolv.conf');
@@ -129,21 +97,19 @@ public function uninstall(): void
$this->pm->restartNetworkManager();
$this->sm->restart('dnsmasq');
- info('Valet DNS changes have been rolled back');
+ Writer::info('Valet DNS changes have been rolled back');
}
/**
* Install and configure DnsMasq.
*/
- private function lockResolvConf(bool $lock = true): void
+ private function lockResolvConf(): void
{
- $arg = $lock ? '+i' : '-i';
-
if (!$this->files->isLink($this->resolvconf)) {
$this->cli->run(
- "chattr {$arg} {$this->resolvconf}",
+ "chattr -i $this->resolvconf",
function ($code, $msg) {
- warning($msg);
+ Writer::warn($msg);
}
);
}
@@ -202,14 +168,23 @@ private function dnsmasqSetup(): void
$this->files->uncommentLine('IGNORE_RESOLVCONF', '/etc/default/dnsmasq');
- $this->lockResolvConf(false);
+ $this->lockResolvConf();
$this->mergeDns();
$this->files->unlink('/etc/dnsmasq.d/network-manager');
$this->files->backup($this->dnsmasqconf);
- $this->files->putAsUser($this->dnsmasqconf, $this->files->get(__DIR__.'/../stubs/dnsmasq.conf'));
- $this->files->putAsUser($this->dnsmasqOpts, $this->files->get(__DIR__.'/../stubs/dnsmasq_options'));
- $this->files->putAsUser($this->nmConfigPath, $this->files->get(__DIR__.'/../stubs/networkmanager.conf'));
+ $this->files->putAsUser(
+ $this->dnsmasqconf,
+ $this->files->get(VALET_ROOT_PATH.'/cli/stubs/dnsmasq.conf')
+ );
+ $this->files->putAsUser(
+ $this->dnsmasqOpts,
+ $this->files->get(VALET_ROOT_PATH.'/cli/stubs/dnsmasq_options')
+ );
+ $this->files->putAsUser(
+ $this->nmConfigPath,
+ $this->files->get(VALET_ROOT_PATH.'/cli/stubs/networkmanager.conf')
+ );
}
}
diff --git a/cli/Valet/Drivers/BasicValetDriver.php b/cli/Valet/Drivers/BasicValetDriver.php
index 37fa6ac..5741d2f 100644
--- a/cli/Valet/Drivers/BasicValetDriver.php
+++ b/cli/Valet/Drivers/BasicValetDriver.php
@@ -30,7 +30,8 @@ public function isStaticFile(string $sitePath, string $siteName, string $uri)
{
if (file_exists($sitePath.rtrim($uri, '/').'/index.html')) {
return $sitePath.rtrim($uri, '/').'/index.html';
- } elseif ($this->isActualFile($sitePath.$uri)) {
+ }
+ if ($this->isActualFile($sitePath.$uri)) {
return $sitePath.$uri;
}
diff --git a/cli/Valet/Drivers/BasicWithPublicValetDriver.php b/cli/Valet/Drivers/BasicWithPublicValetDriver.php
index 062066b..f98bd5d 100644
--- a/cli/Valet/Drivers/BasicWithPublicValetDriver.php
+++ b/cli/Valet/Drivers/BasicWithPublicValetDriver.php
@@ -22,7 +22,8 @@ public function isStaticFile(string $sitePath, string $siteName, string $uri)
if ($this->isActualFile($publicPath)) {
return $publicPath;
- } elseif (file_exists($publicPath.'/index.html')) {
+ }
+ if (file_exists($publicPath.'/index.html')) {
return $publicPath.'/index.html';
}
diff --git a/cli/Valet/Drivers/Specific/BedrockValetDriver.php b/cli/Valet/Drivers/Specific/BedrockValetDriver.php
index 1c4579d..0c30964 100644
--- a/cli/Valet/Drivers/Specific/BedrockValetDriver.php
+++ b/cli/Valet/Drivers/Specific/BedrockValetDriver.php
@@ -46,6 +46,7 @@ public function frontControllerPath(string $sitePath, string $siteName, string $
/**
* Redirect to uri with trailing slash.
+ * @param mixed $uri
*/
private function forceTrailingSlash($uri)
{
diff --git a/cli/Valet/Drivers/Specific/CraftValetDriver.php b/cli/Valet/Drivers/Specific/CraftValetDriver.php
index 75ef3a0..57a68a5 100644
--- a/cli/Valet/Drivers/Specific/CraftValetDriver.php
+++ b/cli/Valet/Drivers/Specific/CraftValetDriver.php
@@ -16,6 +16,7 @@ public function serves(string $sitePath, string $siteName, string $uri): bool
/**
* Determine the name of the directory where the front controller lives.
+ * @param mixed $sitePath
*/
public function frontControllerDirectory($sitePath): string
{
diff --git a/cli/Valet/Drivers/Specific/DrupalValetDriver.php b/cli/Valet/Drivers/Specific/DrupalValetDriver.php
index fe7151a..72a3d27 100644
--- a/cli/Valet/Drivers/Specific/DrupalValetDriver.php
+++ b/cli/Valet/Drivers/Specific/DrupalValetDriver.php
@@ -72,6 +72,7 @@ public function frontControllerPath(string $sitePath, string $siteName, string $
/**
* Add any matching subdirectory to the site path.
+ * @param mixed $sitePath
*/
public function addSubdirectory($sitePath): string
{
diff --git a/cli/Valet/Drivers/Specific/KirbyValetDriver.php b/cli/Valet/Drivers/Specific/KirbyValetDriver.php
index 55918a9..ccce5e8 100644
--- a/cli/Valet/Drivers/Specific/KirbyValetDriver.php
+++ b/cli/Valet/Drivers/Specific/KirbyValetDriver.php
@@ -21,7 +21,8 @@ public function isStaticFile(string $sitePath, string $siteName, string $uri)
{
if ($this->isActualFile($staticFilePath = $sitePath.$uri)) {
return $staticFilePath;
- } elseif ($this->isActualFile($staticFilePath = $sitePath.'/public'.$uri)) {
+ }
+ if ($this->isActualFile($staticFilePath = $sitePath.'/public'.$uri)) {
return $staticFilePath;
}
diff --git a/cli/Valet/Drivers/Specific/StatamicV2ValetDriver.php b/cli/Valet/Drivers/Specific/StatamicV2ValetDriver.php
index cbe00e1..ab3d1e5 100644
--- a/cli/Valet/Drivers/Specific/StatamicV2ValetDriver.php
+++ b/cli/Valet/Drivers/Specific/StatamicV2ValetDriver.php
@@ -21,11 +21,14 @@ public function isStaticFile(string $sitePath, string $siteName, string $uri)
{
if (strpos($uri, '/site') === 0 && strpos($uri, '/site/themes') !== 0) {
return false;
- } elseif (strpos($uri, '/local') === 0 || strpos($uri, '/statamic') === 0) {
+ }
+ if (strpos($uri, '/local') === 0 || strpos($uri, '/statamic') === 0) {
return false;
- } elseif ($this->isActualFile($staticFilePath = $sitePath.$uri)) {
+ }
+ if ($this->isActualFile($staticFilePath = $sitePath.$uri)) {
return $staticFilePath;
- } elseif ($this->isActualFile($staticFilePath = $sitePath.'/public'.$uri)) {
+ }
+ if ($this->isActualFile($staticFilePath = $sitePath.'/public'.$uri)) {
return $staticFilePath;
}
diff --git a/cli/Valet/Drivers/Specific/SymfonyValetDriver.php b/cli/Valet/Drivers/Specific/SymfonyValetDriver.php
index 4be7c66..31c0e15 100644
--- a/cli/Valet/Drivers/Specific/SymfonyValetDriver.php
+++ b/cli/Valet/Drivers/Specific/SymfonyValetDriver.php
@@ -23,7 +23,8 @@ public function isStaticFile(string $sitePath, string $siteName, string $uri)
{
if ($this->isActualFile($staticFilePath = $sitePath.'/web/'.$uri)) {
return $staticFilePath;
- } elseif ($this->isActualFile($staticFilePath = $sitePath.'/public/'.$uri)) {
+ }
+ if ($this->isActualFile($staticFilePath = $sitePath.'/public/'.$uri)) {
return $staticFilePath;
}
diff --git a/cli/Valet/Drivers/Specific/WordPressValetDriver.php b/cli/Valet/Drivers/Specific/WordPressValetDriver.php
index 932ea05..5bb2adc 100644
--- a/cli/Valet/Drivers/Specific/WordPressValetDriver.php
+++ b/cli/Valet/Drivers/Specific/WordPressValetDriver.php
@@ -28,6 +28,7 @@ public function frontControllerPath(string $sitePath, string $siteName, string $
/**
* Redirect to uri with trailing slash.
+ * @param mixed $uri
*/
private function forceTrailingSlash($uri): ?string
{
diff --git a/cli/Valet/Drivers/ValetDriver.php b/cli/Valet/Drivers/ValetDriver.php
index b46ff85..4217cf5 100755
--- a/cli/Valet/Drivers/ValetDriver.php
+++ b/cli/Valet/Drivers/ValetDriver.php
@@ -52,10 +52,10 @@ public static function assign(string $sitePath, string $siteName, string $uri):
foreach ($drivers as $driver) {
if ($driver === 'LocalValetDriver') {
- $driver = new $driver;
+ $driver = new $driver();
} else {
$className = "Valet\Drivers\\{$driver}";
- $driver = new $className;
+ $driver = new $className();
}
if ($driver->serves($sitePath, $siteName, $driver->mutateUri($uri))) {
@@ -72,7 +72,7 @@ public static function assign(string $sitePath, string $siteName, string $uri):
public static function customSiteDriver(string $sitePath): ?string
{
if (!file_exists($sitePath.'/LocalValetDriver.php')) {
- return null; // TODO: Validate this.
+ return null;
}
require_once $sitePath.'/LocalValetDriver.php';
diff --git a/cli/Valet/Facades/Configuration.php b/cli/Valet/Facades/Configuration.php
index d7966e7..d2ae3b5 100644
--- a/cli/Valet/Facades/Configuration.php
+++ b/cli/Valet/Facades/Configuration.php
@@ -7,23 +7,12 @@
*
* @method static void install()
* @method static void uninstall()
- * @method static void createConfigurationDirectory()
- * @method static void createDriversDirectory()
- * @method static void createSitesDirectory()
- * @method static void createExtensionsDirectory()
- * @method static void createLogDirectory()
- * @method static void createCertificatesDirectory()
- * @method static void writeBaseConfiguration()
- * @method static void addPath(string $path, bool $prepend = false)
- * @method static void prependPath(string $path)
- * @method static void removePath(string $path)
* @method static void prune()
- * @method static array read()
* @method static mixed get(string $key, mixed $default = null)
* @method static mixed set(string $key, mixed $default = null)
- * @method static array updateKey(string $key, mixed $value)
- * @method static void write(array $config)
- * @method static string path()
+ * @method static void addPath(string $path, bool $prepend = false)
+ * @method static void removePath(string $path)
+ * @method static string parseDomain(string $site)
*/
class Configuration extends Facade
{
diff --git a/cli/Valet/Facades/DevTools.php b/cli/Valet/Facades/DevTools.php
index 446859a..4968b65 100644
--- a/cli/Valet/Facades/DevTools.php
+++ b/cli/Valet/Facades/DevTools.php
@@ -5,7 +5,7 @@
/**
* Class DevTools.
*
- * @method static false|string getBin(string $service)
+ * @method static false|string getBin(string $service, array $ignoredServices = [])
* @method static void run(string $folder,string $service)
*/
class DevTools extends Facade
diff --git a/cli/Valet/Facades/Filesystem.php b/cli/Valet/Facades/Filesystem.php
new file mode 100644
index 0000000..1e6d26f
--- /dev/null
+++ b/cli/Valet/Facades/Filesystem.php
@@ -0,0 +1,12 @@
+ secured()
+ * @method static void regenerateSecuredSitesConfig()
+ * @method static void reSecureForNewDomain(string $oldDomain, string $domain)
+ */
+class SiteSecure extends Facade
+{
+}
diff --git a/cli/Valet/Facades/Valet.php b/cli/Valet/Facades/Valet.php
index 4cfefa3..b2bd6b9 100644
--- a/cli/Valet/Facades/Valet.php
+++ b/cli/Valet/Facades/Valet.php
@@ -6,11 +6,13 @@
* Class Valet.
*
* @method static void symlinkToUsersBin()
+ * @method static void symlinkPhpToUsersBin()
* @method static void uninstall()
* @method static array extensions()
* @method static bool onLatestVersion(string $currentVersion)
* @method static string getLatestVersion()
* @method static void environmentSetup()
+ * @method static void migrateConfig()
*/
class Valet extends Facade
{
diff --git a/cli/Valet/Filesystem.php b/cli/Valet/Filesystem.php
index fa16716..f014ee5 100644
--- a/cli/Valet/Filesystem.php
+++ b/cli/Valet/Filesystem.php
@@ -3,10 +3,11 @@
namespace Valet;
use ArrayObject;
-use Valet\Facades\CommandLine;
+use ConsoleComponents\Writer;
use Exception;
use FilesystemIterator;
use Traversable;
+use Valet\Facades\CommandLine;
/**
* Class Filesystem.
@@ -19,8 +20,6 @@ class Filesystem
* @param string $files
*
* @throws Exception
- *
- * @return void
*/
public function remove($files): void
{
@@ -98,7 +97,7 @@ public function touch(string $path, ?string $owner = null): string
{
touch($path);
- if ($owner === null) {
+ if ($owner !== null) {
$this->chown($path, $owner);
}
@@ -145,11 +144,6 @@ public function put(string $path, string $contents, ?string $owner = null): stri
/**
* Write to the given file as the non-root user.
- *
- * @param string $path
- * @param string $contents
- *
- * @return string
*/
public function putAsUser(string $path, string $contents): string
{
@@ -176,6 +170,38 @@ public function appendAsUser(string $path, string $contents): void
$this->append($path, $contents, user());
}
+ /**
+ * Copy the given directory to a new location.
+ */
+ public function copyDirectory(string $from, string $to): void
+ {
+ if ($this->isDir($to)) {
+ Writer::warn('Destination directory already exists');
+ return;
+ }
+
+ $this->mkdir($to);
+ $sourceContents = $this->scandir($from);
+
+ foreach ($sourceContents as $sourceContent) {
+ if ($sourceContent == '.' || $sourceContent == '..') {
+ continue;
+ }
+
+ $sourcePath = $from . '/' . $sourceContent;
+ $destinationPath = $to . '/' . $sourceContent;
+
+ if (!$this->isLink($sourcePath) && $this->isDir($sourcePath)) {
+ $this->copyDirectory($sourcePath, $destinationPath);
+ } elseif ($this->isLink($sourcePath)) {
+ $sourcePath = $this->readLink($sourcePath);
+ $this->symlink($sourcePath, $destinationPath);
+ } else {
+ $this->copy($sourcePath, $destinationPath);
+ }
+ }
+ }
+
/**
* Copy the given file to a new location.
*/
@@ -312,10 +338,6 @@ public function isLink(string $path): bool
/**
* Resolve the given symbolic link.
- *
- * @param string $path
- *
- * @return string
*/
public function readLink(string $path): string
{
@@ -330,10 +352,6 @@ public function readLink(string $path): string
/**
* Remove all the broken symbolic links at the given path.
- *
- * @param string $path
- *
- * @return void
*/
public function removeBrokenLinksAt(string $path): void
{
@@ -361,16 +379,16 @@ public function scandir(string $path): array
{
return collect(scandir($path))
->reject(function ($file) {
- return in_array($file, ['.', '..']);
+ return in_array($file, ['.', '..', '.keep']);
})->values()->all();
}
/**
* @param array|string $files
*
- * @return ArrayObject
+ * @return ArrayObject|Traversable
*/
- private function toIterator($files): ArrayObject
+ private function toIterator($files)
{
if (!$files instanceof Traversable) {
$files = new ArrayObject(is_array($files) ? $files : [$files]);
diff --git a/cli/Valet/Mailpit.php b/cli/Valet/Mailpit.php
index b2eb7ed..5d69d96 100644
--- a/cli/Valet/Mailpit.php
+++ b/cli/Valet/Mailpit.php
@@ -5,9 +5,9 @@
use DomainException;
use Valet\Contracts\PackageManager;
use Valet\Contracts\ServiceManager;
-use Valet\Facades\CommandLine;
use Valet\Facades\Configuration;
-use Valet\Facades\Site;
+use Valet\Facades\SiteProxy as SiteProxyFacade;
+use Valet\Facades\SiteSecure as SiteSecureFacade;
class Mailpit
{
@@ -30,16 +30,11 @@ class Mailpit
/**
* @var string
*/
- const SERVICE_NAME = 'mailpit';
+ public const SERVICE_NAME = 'mailpit';
/**
* Create a new Mailpit instance.
*
- * @param PackageManager $pm
- * @param ServiceManager $sm
- * @param CommandLine $cli
- * @param Filesystem $files
- *
* @return void
*/
public function __construct(PackageManager $pm, ServiceManager $sm, CommandLine $cli, Filesystem $files)
@@ -61,22 +56,23 @@ public function install(): void
$this->sm->start(self::SERVICE_NAME);
- if (!$this->sm->disabled('Mailpit')) {
- $this->sm->disable('Mailpit');
- if ($this->files->exists('/opt/valet-linux/Mailpit')) {
- $this->files->remove('/opt/valet-linux/Mailpit');
- }
- $domain = Configuration::get('domain');
- if ($this->files->exists(VALET_HOME_PATH."/Nginx/Mailpit.$domain")) {
- Site::proxyDelete("Mailpit.$domain");
+ try {
+ if (!$this->sm->disabled('mailhog')) {
+ $this->sm->disable('mailhog');
+ if ($this->files->exists('/opt/valet-linux/mailhog')) {
+ $this->files->remove('/opt/valet-linux/mailhog');
+ }
+ $domain = Configuration::get('domain');
+ if ($this->files->exists(VALET_HOME_PATH . "/Nginx/mailhog.$domain")) {
+ SiteSecureFacade::unsecure("mailhog.$domain");
+ }
}
+ } catch (\DomainException $e) {
}
}
/**
* Start the Mailpit service.
- *
- * @return void
*/
public function start(): void
{
@@ -85,8 +81,6 @@ public function start(): void
/**
* Restart the Mailpit service.
- *
- * @return void
*/
public function restart(): void
{
@@ -95,8 +89,6 @@ public function restart(): void
/**
* Stop the Mailpit service.
- *
- * @return void
*/
public function stop(): void
{
@@ -105,8 +97,6 @@ public function stop(): void
/**
* Mailpit service status.
- *
- * @return void
*/
public function status(): void
{
@@ -115,8 +105,6 @@ public function status(): void
/**
* Prepare Mailpit for uninstall.
- *
- * @return void
*/
public function uninstall(): void
{
@@ -140,15 +128,13 @@ private function ensureInstalled(): void
*/
private function createService(): void
{
- info('Installing Mailpit service...');
-
$servicePath = '/etc/init.d/mailpit';
- $serviceFile = VALET_ROOT_PATH.'/cli/stubs/init/mailpit.sh';
+ $serviceFile = VALET_ROOT_PATH . '/cli/stubs/init/mailpit.sh';
$hasSystemd = $this->sm->isSystemd();
if ($hasSystemd) {
$servicePath = '/etc/systemd/system/mailpit.service';
- $serviceFile = VALET_ROOT_PATH.'/cli/stubs/init/mailpit';
+ $serviceFile = VALET_ROOT_PATH . '/cli/stubs/init/mailpit';
}
$this->files->put(
@@ -172,12 +158,9 @@ private function updateDomain(): void
{
$domain = Configuration::get('domain');
- Site::proxyCreate("mails.$domain", 'http://localhost:8025', true);
+ SiteProxyFacade::proxyCreate("mails.$domain", 'http://localhost:8025', true);
}
- /**
- * @return bool
- */
private function isAvailable(): bool
{
try {
diff --git a/cli/Valet/Mysql.php b/cli/Valet/Mysql.php
index 1f04a48..b9947bf 100644
--- a/cli/Valet/Mysql.php
+++ b/cli/Valet/Mysql.php
@@ -2,13 +2,8 @@
namespace Valet;
+use ConsoleComponents\Writer;
use PDO;
-use Symfony\Component\Console\Helper\HelperSet;
-use Symfony\Component\Console\Helper\QuestionHelper;
-use Symfony\Component\Console\Input\ArgvInput;
-use Symfony\Component\Console\Output\ConsoleOutput;
-use Symfony\Component\Console\Question\ConfirmationQuestion;
-use Symfony\Component\Console\Question\Question;
use Valet\Contracts\PackageManager;
use Valet\Contracts\ServiceManager;
use Valet\Facades\PhpFpm as PhpFpmFacade;
@@ -17,52 +12,21 @@
class Mysql
{
+ private const DATABASE_USER = 'valet';
+ public CommandLine $cli;
+ public Filesystem $files;
+ public PackageManager $pm;
+ public ServiceManager $sm;
+ public Configuration $configuration;
/**
- * @var string
+ * @var array|string[]
*/
- const MYSQL_USER = 'valet';
- /**
- * @var CommandLine
- */
- public $cli;
- /**
- * @var Filesystem
- */
- public $files;
- /**
- * @var PackageManager
- */
- public $pm;
- /**
- * @var ServiceManager
- */
- public $sm;
- /**
- * @var Configuration
- */
- public $configuration;
- /**
- * @var string[]
- */
- public $systemDatabases = ['sys', 'performance_schema', 'information_schema', 'mysql'];
- /**
- * @var PDO
- */
- private $pdoConnection = false;
- /**
- * @var string
- */
- protected $currentPackage = '';
+ public array $systemDatabases = ['sys', 'performance_schema', 'information_schema', 'mysql'];
+ private ?PDO $pdoConnection = null;
+ private ?string $currentPackage = null;
/**
* Create a new instance.
- *
- * @param PackageManager $pm
- * @param ServiceManager $sm
- * @param CommandLine $cli
- * @param Filesystem $files
- * @param Configuration $configuration
- * @param Site $site
*/
public function __construct(
PackageManager $pm,
@@ -76,69 +40,52 @@ public function __construct(
$this->sm = $sm;
$this->files = $files;
$this->configuration = $configuration;
- if ($this->pm->installed($this->pm->mysqlPackageName)) {
- $this->currentPackage = $this->pm->mysqlPackageName;
- }
- if ($this->pm->installed($this->pm->mariaDBPackageName)) {
- $this->currentPackage = $this->pm->mariaDBPackageName;
+
+ if ($this->pm->installed($packageName = $this->pm->packageName('mysql'))) {
+ $this->currentPackage = $packageName;
+ } elseif ($this->pm->installed($packageName = $this->pm->packageName('mariadb'))) {
+ $this->currentPackage = $packageName;
}
}
/**
* Install the service.
*/
- public function install($useMariaDB = false)
+ public function install(bool $useMariaDB = false): void
{
- if ($this->pm instanceof Pacman || $this->pm instanceof Dnf) {
- $useMariaDB = true;
- }
- $package = $useMariaDB ? $this->pm->mariaDBPackageName : $this->pm->mysqlPackageName;
- $this->currentPackage = $package;
- $service = $this->serviceName();
- if (!$this->pm instanceof Pacman && !extension_loaded('mysql')) {
- $phpVersion = PhpFpmFacade::getCurrentVersion();
- $this->pm->ensureInstalled("php{$phpVersion}-mysql");
- }
-
- if ($package === $this->pm->mariaDBPackageName
- && $this->pm->installed($this->pm->mysqlPackageName)
- ) {
- warning('MySQL is already installed, please remove --mariadb flag and try again!');
- return;
- }
-
- if ($package === $this->pm->mysqlPackageName
- && $this->pm->installed($this->pm->mariaDBPackageName)
- ) {
- warning('MariaDB is already installed, please add --mariadb flag and try again!');
- return;
+ if ($this->currentPackage === null) {
+ if ($this->pm instanceof Pacman || $this->pm instanceof Dnf) {
+ $useMariaDB = true;
+ }
+ $package = $useMariaDB ? $this->pm->packageName('mariadb') : $this->pm->packageName('mysql');
+ $this->currentPackage = $package;
+ if (!$this->pm instanceof Pacman && !extension_loaded('mysql')) {
+ $phpVersion = PhpFpmFacade::getCurrentVersion();
+ $this->pm->ensureInstalled("php{$phpVersion}-mysql");
+ }
}
- if ($this->pm->installed($package)) {
- $config = $this->configuration->read();
- if (!isset($config['mysql'])) {
- $config['mysql'] = [];
- }
- if (!isset($config['mysql']['password'])) {
- info('Looks like MySQL/MariaDB already installed to your system');
+ if ($this->pm->installed($this->currentPackage)) {
+ /** @var array $config */
+ $config = $this->configuration->get('mysql', []);
+ if (!isset($config['password'])) {
+ Writer::info('Looks like MySQL/MariaDB already installed to your system');
$this->configure();
}
} else {
- $this->pm->installOrFail($package);
- $this->sm->enable($service);
- $this->stop();
+ $this->pm->installOrFail($this->currentPackage);
+ $this->sm->enable($this->serviceName());
if ($this->pm instanceof Pacman) {
- // Configure data directory.
+ $this->stop();
$this->configureDataDirectory();
+ $this->restart();
+ }
+
+ /** @var ?string $password */
+ $password = Writer::ask(\sprintf('Please enter new password for [%s] database user', self::DATABASE_USER));
+ if ($password === null) {
+ $password = '';
}
- $this->restart();
- $input = new ArgvInput();
- $output = new ConsoleOutput();
- $question = new Question('Please enter new password for `'.self::MYSQL_USER.'` database user: ');
- $helper = new HelperSet([new QuestionHelper()]);
- $question->setHidden(true);
- $helper = $helper->get('question');
- $password = $helper->ask($input, $output, $question);
$this->createValetUser($password);
}
}
@@ -168,94 +115,81 @@ public function uninstall(): void
}
/**
- * Print table of exists databases.
+ * Configure Database user for Valet.
*/
- public function listDatabases(): void
+ public function configure(bool $force = false): void
{
- table(['Database'], $this->getDatabases());
- }
+ /** @var array $config */
+ $config = $this->configuration->get('mysql', []);
- /**
- * Import Mysql database from file.
- */
- public function importDatabase(string $file, string $database, bool $isDatabaseExists): void
- {
- $database = $this->getDatabaseName($database);
+ if (!$force && isset($config['password'])) {
+ Writer::info('Valet database user is already configured. Use --force to reconfigure database user.');
+ return;
+ }
- if (!$isDatabaseExists) {
- $this->createDatabase($database);
+ $defaultUser = null;
+ if (!empty($config['user'])) {
+ $defaultUser = $config['user'];
}
- $gzip = '';
- $sqlFile = '';
- if (\stristr($file, '.gz')) {
- $file = escapeshellarg($file);
- $gzip = "zcat {$file} | ";
- } else {
- $file = escapeshellarg($file);
- $sqlFile = " < {$file}";
+ /** @var string $user */
+ $user = Writer::ask('Please enter MySQL/MariaDB user:', $defaultUser);
+
+ /** @var string $password */
+ $password = Writer::ask('Please enter MySQL/MariaDB password:');
+
+ $connection = $this->validateCredentials($user, $password);
+ if (!$connection) {
+ $confirm = Writer::confirm('Would you like to try again?', true);
+ if (!$confirm) {
+ Writer::warn('Valet database user is not configured');
+ return;
+ }
+ $this->configure($force);
+ return;
}
- $database = escapeshellarg($database);
- $credentials = $this->getCredentials();
- $this->cli->run("{$gzip}mysql -u {$credentials['user']} -p{$credentials['password']} {$database} {$sqlFile}");
+ $config['user'] = $user;
+ $config['password'] = $password;
+ $this->configuration->set('mysql', $config);
+ Writer::info('Database user configured successfully');
}
/**
- * Drop Mysql database.
+ * Create Mysql database.
*/
- public function dropDatabase(string $name): bool
+ public function createDatabase(string $name): bool
{
- $name = $this->getDatabaseName($name);
-
- if (!$this->isDatabaseExists($name)) {
- warning("Database [$name] does not exists!");
-
+ if ($this->isDatabaseExists($name)) {
+ Writer::warn("Database [$name] is already exists!");
return false;
}
- $dbDropped = $this->query('DROP DATABASE `'.$name.'`') ? true : false;
-
- if (!$dbDropped) {
- warning('Error dropping database');
-
+ $isCreated = (bool)$this->query('CREATE DATABASE IF NOT EXISTS `'.$name.'`');
+ if (!$isCreated) {
+ Writer::warn('Error creating database');
return false;
}
- info("Database [{$name}] dropped successfully");
-
return true;
}
/**
- * Create Mysql database.
+ * Drop Mysql database.
*/
- public function createDatabase(string $name): void
+ public function dropDatabase(string $name): bool
{
- if ($this->isDatabaseExists($name)) {
- warning("Database [$name] is already exists!");
-
- return;
+ if (!$this->isDatabaseExists($name)) {
+ Writer::warn("Database [$name] does not exists!");
+ return false;
}
- try {
- $name = $this->getDatabaseName($name);
- if ($this->query('CREATE DATABASE IF NOT EXISTS `'.$name.'`')) {
- info("Database [{$name}] created successfully");
- }
- } catch (\Exception $exception) {
- warning('Error while creating database!');
- }
- }
+ $dbDropped = (bool)$this->query('DROP DATABASE `'.$name.'`');
- /**
- * Check if database already exists.
- */
- public function isDatabaseExists(string $name): bool
- {
- $name = $this->getDatabaseName($name);
- $query = $this->query("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '{$name}'");
- $query->execute();
+ if (!$dbDropped) {
+ Writer::warn('Error dropping database');
+ return false;
+ }
- return (bool) $query->rowCount();
+ return true;
}
/**
@@ -263,8 +197,6 @@ public function isDatabaseExists(string $name): bool
*/
public function exportDatabase(string $database, bool $exportSql = false): array
{
- $database = $this->getDatabaseName($database);
-
$filename = $database.'-'.\date('Y-m-d-H-i-s', \time());
if ($exportSql) {
@@ -289,33 +221,51 @@ public function exportDatabase(string $database, bool $exportSql = false): array
}
/**
- * Get database name via name or current dir.
- */
- private function getDatabaseName(string $database = ''): string
- {
- return $database ?: $this->getDirName();
- }
-
- /**
- * Get current dir name.
+ * Import Mysql database from file.
*/
- private function getDirName(): string
+ public function importDatabase(string $file, string $database): void
{
- $gitDir = $this->cli->runAsUser('git rev-parse --show-toplevel 2>/dev/null');
-
- if ($gitDir) {
- return \trim(\basename($gitDir));
+ $isExistsDatabase = false;
+ // check if database already exists.
+ if ($this->isDatabaseExists($database)) {
+ $confirm = Writer::confirm('Database already exists, are you sure you want to continue?');
+ if (!$confirm) {
+ Writer::warn('Aborted');
+ return;
+ }
+ $isExistsDatabase = true;
}
- return \trim(\basename(\getcwd()));
+ if (!$isExistsDatabase) {
+ $this->createDatabase($database);
+ }
+ $gzip = '';
+ $sqlFile = '';
+ if (\stristr($file, '.gz')) {
+ $file = escapeshellarg($file);
+ $gzip = "zcat {$file} | ";
+ } else {
+ $file = escapeshellarg($file);
+ $sqlFile = " < {$file}";
+ }
+ $database = escapeshellarg($database);
+ $credentials = $this->getCredentials();
+ $this->cli->run(
+ \sprintf(
+ '%smysql -u %s -p%s %s %s',
+ $gzip,
+ $credentials['user'],
+ $credentials['password'],
+ $database,
+ $sqlFile
+ )
+ );
}
/**
- * Get exists databases.
- *
- * @return array
+ * Get a list of databases.
*/
- private function getDatabases(): array
+ public function getDatabases(): array
{
$result = $this->query('SHOW DATABASES');
@@ -331,6 +281,17 @@ private function getDatabases(): array
})->toArray();
}
+ /**
+ * Check if database already exists.
+ */
+ private function isDatabaseExists(string $name): bool
+ {
+ $query = $this->query("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$name'");
+ $query->execute();
+
+ return (bool) $query->rowCount();
+ }
+
/**
* Run Mysql query.
*
@@ -343,7 +304,7 @@ private function query(string $query)
try {
return $link->query($query);
} catch (\PDOException $e) {
- warning($e->getMessage());
+ Writer::warn($e->getMessage());
}
}
@@ -363,7 +324,7 @@ private function validateCredentials(string $username, string $password): bool
return true;
} catch (\PDOException $e) {
- warning('Connection failed due to `'.$e->getMessage().'`');
+ Writer::error('Invalid database credentials');
return false;
}
@@ -391,7 +352,7 @@ private function getConnection(): PDO
return $this->pdoConnection;
} catch (\PDOException $e) {
- warning('Failed to connect MySQL due to :`'.$e->getMessage().'`');
+ Writer::warn('Failed to connect MySQL due to :`'.$e->getMessage().'`');
exit;
}
}
@@ -412,65 +373,12 @@ private function configureDataDirectory(): void
$this->cli->run(
'sudo mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql',
function ($statusCode, $output) {
- output(\sprintf('%s: %s', $statusCode, $output));
+ Writer::error(\sprintf('%s: %s', $statusCode, $output));
}
);
$this->restart();
}
- /**
- * Configure Database user for Valet.
- */
- private function configure(bool $force = false): void
- {
- $config = $this->configuration->read();
- if (!isset($config['mysql'])) {
- $config['mysql'] = [];
- }
-
- if (!$force && isset($config['mysql']['password'])) {
- info('Valet database user is already configured. Use --force to reconfigure database user.');
-
- return;
- }
- $input = new ArgvInput();
- $output = new ConsoleOutput();
- if (empty($config['mysql']['user'])) {
- $question = new Question('Please enter MySQL/MariaDB user: ');
- } else {
- $question = new Question(
- 'Please enter MySQL/MariaDB user [current: '.$config['mysql']['user'].']: ',
- $config['mysql']['user']
- );
- }
- $helper = new HelperSet([new QuestionHelper()]);
- $helper = $helper->get('question');
- $user = $helper->ask($input, $output, $question);
- $question = new Question('Please enter MySQL/MariaDB password: ');
- $helper = new HelperSet([new QuestionHelper()]);
- $question->setHidden(true);
- $helper = $helper->get('question');
- $password = $helper->ask($input, $output, $question);
-
- $connection = $this->validateCredentials($user, $password);
- if (!$connection) {
- $question = new ConfirmationQuestion('Would you like to try again? [Y/n] ', true);
- if (!$helper->ask($input, $output, $question)) {
- warning('Valet database user is not configured!');
-
- return;
- } else {
- $this->configure($force);
-
- return;
- }
- }
- $config['mysql']['user'] = $user;
- $config['mysql']['password'] = $password;
- $this->configuration->write($config);
- info('Database user configured successfully!');
- }
-
private function serviceName(): string
{
if ($this->isMariaDB()) {
@@ -482,7 +390,7 @@ private function serviceName(): string
private function isMariaDB(): bool
{
- return $this->currentPackage === $this->pm->mariaDBPackageName;
+ return $this->currentPackage === $this->pm->packageName('mariadb');
}
/**
@@ -491,45 +399,46 @@ private function isMariaDB(): bool
private function createValetUser(string $password): void
{
$success = true;
- $query = "sudo mysql -e \"CREATE USER '".self::MYSQL_USER."'@'localhost' IDENTIFIED WITH mysql_native_password BY '".$password."';GRANT ALL PRIVILEGES ON *.* TO '".self::MYSQL_USER."'@'localhost' WITH GRANT OPTION;FLUSH PRIVILEGES;\"";
+ $query = "sudo mysql -e \"CREATE USER '".self::DATABASE_USER."'@'localhost' IDENTIFIED WITH mysql_native_password BY '".$password."';GRANT ALL PRIVILEGES ON *.* TO '".self::DATABASE_USER."'@'localhost' WITH GRANT OPTION;FLUSH PRIVILEGES;\"";
if ($this->isMariaDB()) {
- $query = "sudo mysql -e \"CREATE USER '".self::MYSQL_USER."'@'localhost' IDENTIFIED BY '".$password."';GRANT ALL PRIVILEGES ON *.* TO '".self::MYSQL_USER."'@'localhost' WITH GRANT OPTION;FLUSH PRIVILEGES;\"";
+ $query = "sudo mysql -e \"CREATE USER '".self::DATABASE_USER."'@'localhost' IDENTIFIED BY '".$password."';GRANT ALL PRIVILEGES ON *.* TO '".self::DATABASE_USER."'@'localhost' WITH GRANT OPTION;FLUSH PRIVILEGES;\"";
}
$this->cli->run(
$query,
function ($statusCode, $error) use (&$success) {
- warning('Setting password for valet user failed due to `['.$statusCode.'] '.$error.'`');
+ Writer::warn('Setting password for valet user failed due to `['.$statusCode.'] '.$error.'`');
$success = false;
}
);
if ($success !== false) {
- $config = $this->configuration->read();
- if (!isset($config['mysql'])) {
- $config['mysql'] = [];
- }
- $config['mysql']['user'] = self::MYSQL_USER;
- $config['mysql']['password'] = $password;
- $this->configuration->write($config);
+ /** @var array $config */
+ $config = $this->configuration->get('mysql', []);
+
+ $config['user'] = self::DATABASE_USER;
+ $config['password'] = $password;
+ $this->configuration->set('mysql', $config);
}
}
/**
* Returns the stored password from the config. If not configured returns the default root password.
+ * @return array{user: string, password: string}
*/
private function getCredentials(): array
{
- $config = $this->configuration->read();
- if (!isset($config['mysql']['password']) && !is_null($config['mysql']['password'])) {
- warning('Valet database user is not configured!');
+ /** @var array $config */
+ $config = $this->configuration->get('mysql', []);
+ if (!isset($config['password']) && $config['password'] !== null) {
+ Writer::warn('Valet database user is not configured!');
exit;
}
// For previously installed user.
- if (empty($config['mysql']['user'])) {
- $config['mysql']['user'] = 'root';
+ if (empty($config['user'])) {
+ $config['user'] = 'root';
}
- return ['user' => $config['mysql']['user'], 'password' => $config['mysql']['password']];
+ return ['user' => $config['user'], 'password' => $config['password']];
}
}
diff --git a/cli/Valet/Nginx.php b/cli/Valet/Nginx.php
index 0ce1f94..39dc117 100644
--- a/cli/Valet/Nginx.php
+++ b/cli/Valet/Nginx.php
@@ -2,7 +2,7 @@
namespace Valet;
-use Tightenco\Collect\Support\Collection;
+use Illuminate\Support\Collection;
use Valet\Contracts\PackageManager;
use Valet\Contracts\ServiceManager;
use Valet\Facades\PhpFpm as PhpFpmFacade;
@@ -30,40 +30,33 @@ class Nginx
*/
public $configuration;
/**
- * @var Site
+ * @var SiteSecure
*/
- public $site;
+ public $siteSecure;
/**
* @var string
*/
- const NGINX_CONF = '/etc/nginx/nginx.conf';
- const SITES_AVAILABLE_CONF = '/etc/nginx/sites-available/valet.conf';
- const SITES_ENABLED_CONF = '/etc/nginx/sites-enabled/valet.conf';
+ public const NGINX_CONF = '/etc/nginx/nginx.conf';
+ public const SITES_AVAILABLE_CONF = '/etc/nginx/sites-available/valet.conf';
+ public const SITES_ENABLED_CONF = '/etc/nginx/sites-enabled/valet.conf';
/**
* Create a new Nginx instance.
*
- * @param PackageManager $pm
- * @param ServiceManager $sm
- * @param CommandLine $cli
- * @param Filesystem $files
- * @param Configuration $configuration
- * @param Site $site
- *
* @return void
*/
public function __construct(
PackageManager $pm,
ServiceManager $sm,
- CommandLine $cli,
- Filesystem $files,
- Configuration $configuration,
- Site $site
+ CommandLine $cli,
+ Filesystem $files,
+ Configuration $configuration,
+ SiteSecure $siteSecure
) {
$this->cli = $cli;
$this->pm = $pm;
$this->sm = $sm;
- $this->site = $site;
+ $this->siteSecure = $siteSecure;
$this->files = $files;
$this->configuration = $configuration;
}
@@ -94,7 +87,7 @@ public function updatePort(string $newPort): void
'VALET_HOME_PATH' => VALET_HOME_PATH,
'VALET_SERVER_PATH' => VALET_SERVER_PATH,
'VALET_PORT' => $newPort,
- ], $this->files->get(__DIR__.'/../stubs/valet.conf'));
+ ], $this->files->get(VALET_ROOT_PATH . '/cli/stubs/valet.conf'));
$this->files->putAsUser(self::SITES_AVAILABLE_CONF, $valetConfig);
}
@@ -143,9 +136,9 @@ public function uninstall(): void
*/
public function configuredSites(): Collection
{
- return collect($this->files->scandir(VALET_HOME_PATH.'/Nginx'))
+ return collect($this->files->scandir(VALET_HOME_PATH . '/Nginx'))
->reject(function ($file) {
- return starts_with($file, '.');
+ return str_starts_with($file, '.');
});
}
@@ -158,11 +151,11 @@ public function installServer($phpVersion = null): void
{
$valetConf = strArrayReplace([
'VALET_HOME_PATH' => VALET_HOME_PATH,
- 'VALET_FPM_SOCKET_FILE' => VALET_HOME_PATH.'/'.PhpFpmFacade::socketFileName($phpVersion),
+ 'VALET_FPM_SOCKET_FILE' => VALET_HOME_PATH . '/' . PhpFpmFacade::socketFileName($phpVersion),
'VALET_SERVER_PATH' => VALET_SERVER_PATH,
'VALET_STATIC_PREFIX' => VALET_STATIC_PREFIX,
- 'VALET_PORT' => $this->configuration->read()['port'],
- ], $this->files->get(__DIR__.'/../stubs/valet.conf'));
+ 'VALET_PORT' => $this->configuration->get('port'),
+ ], $this->files->get(VALET_ROOT_PATH . '/cli/stubs/valet.conf'));
$this->files->putAsUser(self::SITES_AVAILABLE_CONF, $valetConf);
if ($this->files->exists('/etc/nginx/sites-enabled/default')) {
@@ -174,7 +167,7 @@ public function installServer($phpVersion = null): void
$this->files->putAsUser(
'/etc/nginx/fastcgi_params',
- $this->files->get(__DIR__.'/../stubs/fastcgi_params')
+ $this->files->get(VALET_ROOT_PATH . '/cli/stubs/fastcgi_params')
);
}
@@ -183,9 +176,9 @@ public function installServer($phpVersion = null): void
*/
private function rewriteSecureNginxFiles(): void
{
- $domain = $this->configuration->read()['domain'];
+ $domain = $this->configuration->get('domain');
- $this->site->resecureForNewDomain($domain, $domain);
+ $this->siteSecure->reSecureForNewDomain($domain, $domain);
}
/**
@@ -207,7 +200,7 @@ private function handleApacheService(): void
*/
private function installConfiguration(): void
{
- $contents = $this->files->get(__DIR__.'/../stubs/nginx.conf');
+ $contents = $this->files->get(VALET_ROOT_PATH . '/cli/stubs/nginx.conf');
$nginxConfig = self::NGINX_CONF;
$pidPath = 'pid /run/nginx.pid';
@@ -237,11 +230,11 @@ private function installConfiguration(): void
*/
private function installNginxDirectory(): void
{
- if (!$this->files->isDir($nginxDirectory = VALET_HOME_PATH.'/Nginx')) {
+ if (!$this->files->isDir($nginxDirectory = VALET_HOME_PATH . '/Nginx')) {
$this->files->mkdirAsUser($nginxDirectory);
}
- $this->files->putAsUser($nginxDirectory.'/.keep', "\n");
+ $this->files->putAsUser($nginxDirectory . '/.keep', "\n");
$this->rewriteSecureNginxFiles();
}
diff --git a/cli/Valet/Ngrok.php b/cli/Valet/Ngrok.php
index 9394cb1..45d5f3a 100644
--- a/cli/Valet/Ngrok.php
+++ b/cli/Valet/Ngrok.php
@@ -2,9 +2,10 @@
namespace Valet;
+use ConsoleComponents\Writer;
use DomainException;
use Exception;
-use Httpful\Request;
+use Valet\Facades\Request as RequestFacade;
class Ngrok
{
@@ -12,38 +13,65 @@ class Ngrok
* @var string
*/
private const TUNNEL_ENDPOINT = 'http://127.0.0.1:4040/api/tunnels';
- /**
- * @var CommandLine
- */
- public $cli;
+ private const BINARY_DOWNLOAD_LINK = 'https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz';
+
+ public CommandLine $cli;
+
+ public Filesystem $files;
/**
* Create a new Ngrok instance.
- *
- * @param CommandLine $cli
*/
- public function __construct(CommandLine $cli)
+ public function __construct(CommandLine $cli, Filesystem $filesystem)
{
$this->cli = $cli;
+ $this->files = $filesystem;
+ }
+
+ /**
+ * Install Ngrok binary
+ * @throws Exception
+ */
+ public function install(): void
+ {
+ if ($this->files->exists(\sprintf('%s/bin/ngrok', VALET_ROOT_PATH))) {
+ return;
+ }
+
+ Writer::twoColumnDetail('Ngrok', 'Installing');
+ $this->files->ensureDirExists(\sprintf('%s/bin', VALET_ROOT_PATH), user());
+
+ $response = RequestFacade::get(self::BINARY_DOWNLOAD_LINK)->send();
+ if ($response->hasErrors()) {
+ Writer::twoColumnDetail('Ngrok', 'Failed');
+ return;
+ }
+ $zipFile = \sprintf('%s/bin/%s', VALET_ROOT_PATH, basename(self::BINARY_DOWNLOAD_LINK));
+
+ $this->files->putAsUser($zipFile, $response->raw_body);
+
+ $phar = new \PharData($zipFile);
+ $phar->extractTo(VALET_ROOT_PATH.'/bin/');
+
+ $this->files->remove($zipFile);
}
/**
* Get the current tunnel URL from the Ngrok API.
* @throws Exception
*/
- public function currentTunnelUrl(): string
+ public function currentTunnelUrl(): ?string
{
return retry(20, function () {
- $body = Request::get(self::TUNNEL_ENDPOINT)->send()->body;
+ $body = RequestFacade::get(self::TUNNEL_ENDPOINT)->send()->body;
// If there are active tunnels on the Ngrok instance we will spin through them and
// find the one responding on HTTP. Each tunnel has an HTTP and a HTTPS address
// but for local testing purposes we just desire the plain HTTP URL endpoint.
if (isset($body->tunnels) && count($body->tunnels) > 0) {
return $this->findHttpTunnelUrl($body->tunnels);
- } else {
- throw new DomainException('Tunnel not established.');
}
+ throw new DomainException('Tunnel not established.');
}, 250);
}
@@ -52,8 +80,9 @@ public function currentTunnelUrl(): string
*/
public function setAuthToken(string $authToken): void
{
- $this->cli->run(__DIR__.'/../../bin/ngrok config add-authtoken '.$authToken);
- info('Ngrok authentication token set.');
+ $this->cli->run(
+ \sprintf('%s/bin/ngrok config add-authtoken %s', VALET_ROOT_PATH, $authToken)
+ );
}
/**
diff --git a/cli/Valet/PackageManagers/Apt.php b/cli/Valet/PackageManagers/Apt.php
index 21f80f8..9efe7af 100644
--- a/cli/Valet/PackageManagers/Apt.php
+++ b/cli/Valet/PackageManagers/Apt.php
@@ -2,11 +2,11 @@
namespace Valet\PackageManagers;
+use ConsoleComponents\Writer;
use DomainException;
use Valet\CommandLine;
use Valet\Contracts\PackageManager;
use Valet\Contracts\ServiceManager;
-use function Valet\output;
class Apt implements PackageManager
{
@@ -18,23 +18,17 @@ class Apt implements PackageManager
* @var ServiceManager
*/
public $serviceManager;
- /**
- * @var string
- */
- public $redisPackageName = 'redis-server';
- /**
- * @var string
- */
- public $mysqlPackageName = 'mysql-server';
- /**
- * @var string
- */
- public $mariaDBPackageName = 'mariadb-server';
+
+ private const PACKAGES = [
+ 'redis' => 'redis-server',
+ 'mysql' => 'mysql-server',
+ 'mariadb' => 'mariadb-server',
+ ];
/**
* @var array
*/
- const PHP_FPM_PATTERN_BY_VERSION = [];
+ public const PHP_FPM_PATTERN_BY_VERSION = [];
/**
* Create a new Apt instance.
@@ -78,10 +72,10 @@ public function ensureInstalled(string $package): void
*/
public function installOrFail(string $package): void
{
- output('['.$package.'] is not installed, installing it now via Apt');
+ Writer::twoColumnDetail($package, 'Installing');
$this->cli->run(trim('apt-get install -y '.$package), function ($exitCode, $errorOutput) use ($package) {
- output(\sprintf('%s: %s', $exitCode, $errorOutput));
+ Writer::error(\sprintf('%s: %s', $exitCode, $errorOutput));
throw new DomainException('Apt was unable to install ['.$package.'].');
});
@@ -138,4 +132,15 @@ public function restartNetworkManager(): void
{
$this->serviceManager->restart(['NetworkManager']);
}
+
+ /**
+ * Get package name by service.
+ */
+ public function packageName(string $name): string
+ {
+ if (isset(self::PACKAGES[$name])) {
+ return self::PACKAGES[$name];
+ }
+ throw new \InvalidArgumentException(\sprintf('Package not found by %s', $name));
+ }
}
diff --git a/cli/Valet/PackageManagers/Dnf.php b/cli/Valet/PackageManagers/Dnf.php
index 905f149..a6b70fc 100644
--- a/cli/Valet/PackageManagers/Dnf.php
+++ b/cli/Valet/PackageManagers/Dnf.php
@@ -2,11 +2,11 @@
namespace Valet\PackageManagers;
+use ConsoleComponents\Writer;
use DomainException;
use Valet\CommandLine;
use Valet\Contracts\PackageManager;
use Valet\Contracts\ServiceManager;
-use function Valet\output;
class Dnf implements PackageManager
{
@@ -18,23 +18,16 @@ class Dnf implements PackageManager
* @var ServiceManager
*/
public $serviceManager;
- /**
- * @var string
- */
- public $redisPackageName = 'redis';
- /**
- * @var string
- */
- public $mysqlPackageName = 'mysql-server';
- /**
- * @var string
- */
- public $mariaDBPackageName = 'mariadb-server';
-
/**
* @var array
*/
- const PHP_FPM_PATTERN_BY_VERSION = [];
+ public const PHP_FPM_PATTERN_BY_VERSION = [];
+
+ private const PACKAGES = [
+ 'redis' => 'redis',
+ 'mysql' => 'mysql-server',
+ 'mariadb' => 'mariadb-server',
+ ];
/**
* Create a new Apt instance.
@@ -72,10 +65,10 @@ public function ensureInstalled(string $package): void
*/
public function installOrFail(string $package): void
{
- output('['.$package.'] is not installed, installing it now via Dnf');
+ Writer::twoColumnDetail($package, 'Installing');
$this->cli->run(trim('dnf install -y '.$package), function ($exitCode, $errorOutput) use ($package) {
- output(\sprintf('%s: %s', $exitCode, $errorOutput));
+ Writer::error(\sprintf('%s: %s', $exitCode, $errorOutput));
throw new DomainException('Dnf was unable to install ['.$package.'].');
});
@@ -132,4 +125,15 @@ public function restartNetworkManager(): void
{
$this->serviceManager->restart('NetworkManager');
}
+
+ /**
+ * Get package name by service.
+ */
+ public function packageName(string $name): string
+ {
+ if (isset(self::PACKAGES[$name])) {
+ return self::PACKAGES[$name];
+ }
+ throw new \InvalidArgumentException(\sprintf('Package not found by %s', $name));
+ }
}
diff --git a/cli/Valet/PackageManagers/Eopkg.php b/cli/Valet/PackageManagers/Eopkg.php
index f0eea65..0cee021 100644
--- a/cli/Valet/PackageManagers/Eopkg.php
+++ b/cli/Valet/PackageManagers/Eopkg.php
@@ -2,11 +2,11 @@
namespace Valet\PackageManagers;
+use ConsoleComponents\Writer;
use DomainException;
use Valet\CommandLine;
use Valet\Contracts\PackageManager;
use Valet\Contracts\ServiceManager;
-use function Valet\output;
class Eopkg implements PackageManager
{
@@ -18,23 +18,16 @@ class Eopkg implements PackageManager
* @var ServiceManager
*/
public $serviceManager;
- /**
- * @var string
- */
- public $redisPackageName = 'redis-server';
- /**
- * @var string
- */
- public $mysqlPackageName = 'mysql-server';
- /**
- * @var string
- */
- public $mariaDBPackageName = 'mariadb-server';
-
/**
* @var array
*/
- const PHP_FPM_PATTERN_BY_VERSION = [];
+ public const PHP_FPM_PATTERN_BY_VERSION = [];
+
+ private const PACKAGES = [
+ 'redis' => 'redis-server',
+ 'mysql' => 'mysql-server',
+ 'mariadb' => 'mariadb-server',
+ ];
/**
* Create a new Eopkg instance.
@@ -78,10 +71,10 @@ public function ensureInstalled(string $package): void
*/
public function installOrFail(string $package): void
{
- output('['.$package.'] is not installed, installing it now via Eopkg');
+ Writer::twoColumnDetail($package, 'Installing');
$this->cli->run(trim('eopkg install -y '.$package), function ($exitCode, $errorOutput) use ($package) {
- output(\sprintf('%s: %s', $exitCode, $errorOutput));
+ Writer::error(\sprintf('%s: %s', $exitCode, $errorOutput));
throw new DomainException('Eopkg was unable to install ['.$package.'].');
});
@@ -138,4 +131,15 @@ public function restartNetworkManager(): void
{
$this->serviceManager->restart('NetworkManager');
}
+
+ /**
+ * Get package name by service.
+ */
+ public function packageName(string $name): string
+ {
+ if (isset(self::PACKAGES[$name])) {
+ return self::PACKAGES[$name];
+ }
+ throw new \InvalidArgumentException(\sprintf('Package not found by %s', $name));
+ }
}
diff --git a/cli/Valet/PackageManagers/PackageKit.php b/cli/Valet/PackageManagers/PackageKit.php
index 987dbd4..d1a897c 100644
--- a/cli/Valet/PackageManagers/PackageKit.php
+++ b/cli/Valet/PackageManagers/PackageKit.php
@@ -2,11 +2,11 @@
namespace Valet\PackageManagers;
+use ConsoleComponents\Writer;
use DomainException;
use Valet\CommandLine;
use Valet\Contracts\PackageManager;
use Valet\Contracts\ServiceManager;
-use function Valet\output;
class PackageKit implements PackageManager
{
@@ -18,23 +18,16 @@ class PackageKit implements PackageManager
* @var ServiceManager
*/
public $serviceManager;
- /**
- * @var string
- */
- public $redisPackageName = 'redis-server';
- /**
- * @var string
- */
- public $mysqlPackageName = 'mysql-server';
- /**
- * @var string
- */
- public $mariaDBPackageName = 'mariadb-server';
-
/**
* @var array
*/
- const PHP_FPM_PATTERN_BY_VERSION = [];
+ public const PHP_FPM_PATTERN_BY_VERSION = [];
+
+ private const PACKAGES = [
+ 'redis' => 'redis-server',
+ 'mysql' => 'mysql-server',
+ 'mariadb' => 'mariadb-server',
+ ];
/**
* Create a new Apt instance.
@@ -78,10 +71,10 @@ public function ensureInstalled(string $package): void
*/
public function installOrFail(string $package): void
{
- output('['.$package.'] is not installed, installing it now via PackageKit');
+ Writer::twoColumnDetail($package, 'Installing');
$this->cli->run(trim('pkcon install -y '.$package), function ($exitCode, $errorOutput) use ($package) {
- output(\sprintf('%s: %s', $exitCode, $errorOutput));
+ Writer::error(\sprintf('%s: %s', $exitCode, $errorOutput));
throw new DomainException('PackageKit was unable to install ['.$package.'].');
});
@@ -145,4 +138,15 @@ public function restartNetworkManager(): void
$this->serviceManager->restart('systemd-resolved');
}
}
+
+ /**
+ * Get package name by service.
+ */
+ public function packageName(string $name): string
+ {
+ if (isset(self::PACKAGES[$name])) {
+ return self::PACKAGES[$name];
+ }
+ throw new \InvalidArgumentException(\sprintf('Package not found by %s', $name));
+ }
}
diff --git a/cli/Valet/PackageManagers/Pacman.php b/cli/Valet/PackageManagers/Pacman.php
index 3d6fd54..5c520b8 100644
--- a/cli/Valet/PackageManagers/Pacman.php
+++ b/cli/Valet/PackageManagers/Pacman.php
@@ -2,11 +2,11 @@
namespace Valet\PackageManagers;
+use ConsoleComponents\Writer;
use DomainException;
use Valet\CommandLine;
use Valet\Contracts\PackageManager;
use Valet\Contracts\ServiceManager;
-use function Valet\output;
class Pacman implements PackageManager
{
@@ -18,23 +18,16 @@ class Pacman implements PackageManager
* @var ServiceManager
*/
public $serviceManager;
- /**
- * @var string
- */
- public $redisPackageName = 'redis';
- /**
- * @var string
- */
- public $mysqlPackageName = 'mysql';
- /**
- * @var string
- */
- public $mariaDBPackageName = 'mariadb';
-
/**
* @var array
*/
- const PHP_FPM_PATTERN_BY_VERSION = [];
+ public const PHP_FPM_PATTERN_BY_VERSION = [];
+
+ private const PACKAGES = [
+ 'redis' => 'redis',
+ 'mysql' => 'mysql',
+ 'mariadb' => 'mariadb',
+ ];
/**
* Create a new Apt instance.
@@ -78,12 +71,12 @@ public function ensureInstalled(string $package): void
*/
public function installOrFail(string $package): void
{
- output('['.$package.'] is not installed, installing it now via Pacman');
+ Writer::twoColumnDetail($package, 'Installing');
$this->cli->run(
trim('pacman --noconfirm --needed -S '.$package),
function ($exitCode, $errorOutput) use ($package) {
- output(\sprintf('%s: %s', $exitCode, $errorOutput));
+ Writer::error(\sprintf('%s: %s', $exitCode, $errorOutput));
throw new DomainException('Pacman was unable to install ['.$package.'].');
}
@@ -143,4 +136,15 @@ public function restartNetworkManager(): void
{
$this->serviceManager->restart('NetworkManager');
}
+
+ /**
+ * Get package name by service.
+ */
+ public function packageName(string $name): string
+ {
+ if (isset(self::PACKAGES[$name])) {
+ return self::PACKAGES[$name];
+ }
+ throw new \InvalidArgumentException(\sprintf('Package not found by %s', $name));
+ }
}
diff --git a/cli/Valet/PackageManagers/Yum.php b/cli/Valet/PackageManagers/Yum.php
index 554aa4d..896f043 100644
--- a/cli/Valet/PackageManagers/Yum.php
+++ b/cli/Valet/PackageManagers/Yum.php
@@ -2,11 +2,11 @@
namespace Valet\PackageManagers;
+use ConsoleComponents\Writer;
use DomainException;
use Valet\CommandLine;
use Valet\Contracts\PackageManager;
use Valet\Contracts\ServiceManager;
-use function Valet\output;
class Yum implements PackageManager
{
@@ -22,19 +22,17 @@ class Yum implements PackageManager
* @var string
*/
public $redisPackageName = 'redis';
- /**
- * @var string
- */
- public $mysqlPackageName = 'mysql-server';
- /**
- * @var string
- */
- public $mariaDBPackageName = 'mariadb-server';
/**
* @var array
*/
- const PHP_FPM_PATTERN_BY_VERSION = [];
+ public const PHP_FPM_PATTERN_BY_VERSION = [];
+
+ private const PACKAGES = [
+ 'redis' => 'redis',
+ 'mysql' => 'mysql-server',
+ 'mariadb' => 'mariadb-server',
+ ];
/**
* Create a new Apt instance.
@@ -72,10 +70,10 @@ public function ensureInstalled(string $package): void
*/
public function installOrFail(string $package): void
{
- output('['.$package.'] is not installed, installing it now via Yum');
+ Writer::twoColumnDetail($package, 'Installing');
$this->cli->run(trim('yum install -y '.$package), function ($exitCode, $errorOutput) use ($package) {
- output(\sprintf('%s: %s', $exitCode, $errorOutput));
+ Writer::error(\sprintf('%s: %s', $exitCode, $errorOutput));
throw new DomainException('Yum was unable to install ['.$package.'].');
});
@@ -132,4 +130,15 @@ public function restartNetworkManager(): void
{
$this->serviceManager->restart('NetworkManager');
}
+
+ /**
+ * Get package name by service.
+ */
+ public function packageName(string $name): string
+ {
+ if (isset(self::PACKAGES[$name])) {
+ return self::PACKAGES[$name];
+ }
+ throw new \InvalidArgumentException(\sprintf('Package not found by %s', $name));
+ }
}
diff --git a/cli/Valet/PhpFpm.php b/cli/Valet/PhpFpm.php
index fb0c3e4..b34f75b 100644
--- a/cli/Valet/PhpFpm.php
+++ b/cli/Valet/PhpFpm.php
@@ -2,44 +2,39 @@
namespace Valet;
-use Exception;
-use Tightenco\Collect\Support\Collection;
+use ConsoleComponents\Writer;
+use Illuminate\Support\Collection;
use Valet\Contracts\PackageManager;
use Valet\Contracts\ServiceManager;
use Valet\Exceptions\VersionException;
use Valet\Facades\DevTools as DevToolsFacade;
-use Valet\Facades\Nginx as NginxFacade;
class PhpFpm
{
- protected $config;
- protected $pm;
- protected $sm;
- protected $cli;
- protected $files;
- protected $site;
- protected $nginx;
-
- const SUPPORTED_PHP_VERSIONS = [
- '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3',
+ protected Configuration $config;
+ protected PackageManager $pm;
+ protected ServiceManager $sm;
+ protected CommandLine $cli;
+ protected Filesystem $files;
+ protected Site $site;
+ protected Nginx $nginx;
+
+ public const SUPPORTED_PHP_VERSIONS = [
+ '8.2', '8.3',
];
- const COMMON_EXTENSIONS = [
+ public const ISOLATION_SUPPORTED_PHP_VERSIONS = [
+ '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', ...self::SUPPORTED_PHP_VERSIONS
+ ];
+
+ public const COMMON_EXTENSIONS = [
'cli', 'mysql', 'gd', 'zip', 'xml', 'curl', 'mbstring', 'pgsql', 'intl', 'posix',
];
- const FPM_CONFIG_FILE_NAME = 'valet.conf';
+ public const FPM_CONFIG_FILE_NAME = 'valet.conf';
/**
* Create a new PHP FPM class instance.
- *
- * @param Configuration $config
- * @param PackageManager $pm
- * @param ServiceManager $sm
- * @param CommandLine $cli
- * @param Filesystem $files
- * @param Site $site
- * @param Nginx $nginx
*/
public function __construct(
Configuration $config,
@@ -51,9 +46,9 @@ public function __construct(
Nginx $nginx
) {
$this->config = $config;
- $this->cli = $cli;
$this->pm = $pm;
$this->sm = $sm;
+ $this->cli = $cli;
$this->files = $files;
$this->site = $site;
$this->nginx = $nginx;
@@ -61,13 +56,14 @@ public function __construct(
/**
* Install and configure PHP FPM.
- * @throws VersionException
*/
public function install(?string $version = null, bool $installExt = true): void
{
$version = $version ?: $this->getCurrentVersion();
$version = $this->normalizePhpVersion($version);
- $this->validateVersion($version);
+ if ($version === '') {
+ return;
+ }
$packageName = $this->pm->getPhpFpmName($version);
if (!$this->pm->installed($packageName)) {
@@ -88,39 +84,39 @@ public function install(?string $version = null, bool $installExt = true): void
/**
* Uninstall PHP FPM valet config.
*/
- public function uninstall(): void
+ public function uninstall(?string $version = null): void
{
- if ($this->files->exists($this->fpmConfigPath().'/'.self::FPM_CONFIG_FILE_NAME)) {
- $this->files->unlink($this->fpmConfigPath().'/'.self::FPM_CONFIG_FILE_NAME);
- $this->stop();
+ $version = $version ?: $this->getCurrentVersion();
+ $version = $this->normalizePhpVersion($version);
+ if ($version === '') {
+ return;
+ }
+
+ $fpmConfPath = $this->fpmConfigPath($version) . '/' . self::FPM_CONFIG_FILE_NAME;
+ if ($this->files->exists($fpmConfPath)) {
+ $this->files->unlink($fpmConfPath);
+ $this->stop($version);
}
}
/**
* Change the php-fpm version.
- * @throws Exception
*/
public function switchVersion(
- string $version = null,
+ string $version,
bool $updateCli = false,
- bool $ignoreExt = false,
- bool $ignoreUpdate = false
+ bool $ignoreExt = false
): void {
- $exception = null;
-
$currentVersion = $this->getCurrentVersion();
// Validate if in use
$version = $this->normalizePhpVersion($version);
- try {
- $this->install($version, !$ignoreExt);
- } catch (Exception $e) {
- $version = $currentVersion;
- $exception = $e;
- }
+ Writer::info('Changing php version...');
+
+ $this->install($version, !$ignoreExt);
- if ($this->sm->disabled($this->serviceName())) {
- $this->sm->enable($this->serviceName());
+ if ($this->sm->disabled($this->serviceName($version))) {
+ $this->sm->enable($this->serviceName($version));
}
$this->config->set('php_version', $version);
@@ -128,24 +124,16 @@ public function switchVersion(
$this->stopIfUnused($currentVersion);
$this->updateNginxConfigFiles($version);
- NginxFacade::restart();
+ $this->nginx->restart();
$this->status($version);
if ($updateCli) {
$this->cli->run("update-alternatives --set php /usr/bin/php$version");
- if (!$ignoreUpdate) {
- $this->handlePackageUpdate($version);
- }
- }
-
- if ($exception) {
- warning('Changing version failed');
-
- throw $exception;
}
}
/**
* Restart the PHP FPM process.
+ * @param null|mixed $version
*/
public function restart($version = null): void
{
@@ -154,6 +142,7 @@ public function restart($version = null): void
/**
* Stop the PHP FPM process.
+ * @param null|mixed $version
*/
public function stop($version = null): void
{
@@ -162,71 +151,13 @@ public function stop($version = null): void
/**
* PHP-FPM service status.
+ * @param null|mixed $version
*/
public function status($version = null): void
{
$this->sm->printStatus($this->serviceName($version));
}
- /**
- * Isolate a given directory to use a specific version of PHP.
- * @throws VersionException
- */
- public function isolateDirectory(string $directory, string $version, bool $secure = false): void
- {
- $site = $this->site->getSiteUrl($directory);
-
- $version = $this->normalizePhpVersion($version);
- $this->validateVersion($version);
-
- $fpmName = $this->pm->getPhpFpmName($version);
- if (!$this->pm->installed($fpmName)) {
- $this->install($version);
- }
-
- $oldCustomPhpVersion = $this->site->customPhpVersion($site); // Example output: "74"
-
- $this->site->isolate($site, $version, $secure);
-
- if ($oldCustomPhpVersion) {
- $this->stopIfUnused($oldCustomPhpVersion);
- }
- $this->restart($version);
- NginxFacade::restart();
-
- info(sprintf('The site [%s] is now using %s.', $site, $version));
- }
-
- /**
- * Remove PHP version isolation for a given directory.
- */
- public function unIsolateDirectory(string $directory): void
- {
- $site = $this->site->getSiteUrl($directory);
-
- $oldCustomPhpVersion = $this->site->customPhpVersion($site); // Example output: "74"
-
- $this->site->removeIsolation($site);
- if ($oldCustomPhpVersion) {
- $this->stopIfUnused($oldCustomPhpVersion);
- }
- NginxFacade::restart();
-
- info(sprintf('The site [%s] is now using the default PHP version.', $site));
- }
-
- /**
- * List isolated directories with version.
- */
- public function isolatedDirectories(): Collection
- {
- return NginxFacade::configuredSites()->filter(function ($item) {
- return strpos($this->files->get(VALET_HOME_PATH.'/Nginx/'.$item), ISOLATED_PHP_VERSION) !== false;
- })->map(function ($item) {
- return ['url' => $item, 'version' => $this->normalizePhpVersion($this->site->customPhpVersion($item))];
- });
- }
-
/**
* Get FPM socket file name for a given PHP version.
*/
@@ -243,9 +174,19 @@ public function socketFileName(string $version = null): string
/**
* Normalize inputs (php-x.x, php@x.x, phpx.x, phpxx) to version (x.x).
*/
- public function normalizePhpVersion($version): string
+ public function normalizePhpVersion(string $version): string
{
- return substr(preg_replace('/(?:php@?)?([0-9+])(?:.)?([0-9+])/i', '$1.$2', (string) $version), 0, 3);
+ preg_match(
+ '/^(?:php[@-]?)?(?[\d]{1}).?(?[\d]{1})$/i',
+ $version,
+ $matches
+ );
+
+ if (!isset($matches['MAJOR_VERSION'], $matches['MINOR_VERSION'])) {
+ return '';
+ }
+
+ return \sprintf('%s.%s', $matches['MAJOR_VERSION'], $matches['MINOR_VERSION']);
}
/**
@@ -263,23 +204,47 @@ public function getCurrentVersion(): string
public function getPhpExecutablePath(string $version = null)
{
if (!$version) {
- return DevToolsFacade::getBin('php');
+ return DevToolsFacade::getBin('php', ['/usr/local/bin/php']);
}
$version = $this->normalizePhpVersion($version);
- return DevToolsFacade::getBin('php'.$version);
+ return DevToolsFacade::getBin('php' . $version, ['/usr/local/bin/php']);
}
public function fpmSocketFile(string $version): string
{
- return VALET_HOME_PATH.'/'.$this->socketFileName($version);
+ return VALET_HOME_PATH . '/' . $this->socketFileName($version);
+ }
+
+ public function updateHomePath(string $oldHomePath, string $newHomePath): void
+ {
+ foreach (self::ISOLATION_SUPPORTED_PHP_VERSIONS as $version) {
+ $confPath = $this->fpmConfigPath($version) . '/' . self::FPM_CONFIG_FILE_NAME;
+ if ($this->files->exists($confPath)) {
+ $valetConf = $this->files->get($confPath);
+ $valetConf = str_replace($oldHomePath, $newHomePath, $valetConf);
+ $this->files->put($confPath, $valetConf);
+ }
+ }
+ }
+
+ /**
+ * Validate PHP version.
+ */
+ public function validateVersion(string $version): bool
+ {
+ if (!in_array($version, self::SUPPORTED_PHP_VERSIONS)) {
+ return false;
+ }
+
+ return true;
}
/**
* Stop a given PHP version, if that specific version isn't being used globally or by any sites.
*/
- private function stopIfUnused(string $version): void
+ public function stopIfUnused(string $version): void
{
$version = $this->normalizePhpVersion($version);
@@ -303,12 +268,12 @@ private function serviceName(string $version = null): string
private function updateNginxConfigFiles(string $version): void
{
//Action 1: Update all separate secured versions
- NginxFacade::configuredSites()->map(function ($file) use ($version) {
- $content = $this->files->get(VALET_HOME_PATH.'/Nginx/'.$file);
+ $this->nginx->configuredSites()->map(function ($file) use ($version) {
+ $content = $this->files->get(VALET_HOME_PATH . '/Nginx/' . $file);
if (!$content) {
return;
}
- if (strpos($content, '# '.ISOLATED_PHP_VERSION) !== false) {
+ if (strpos($content, '# ' . ISOLATED_PHP_VERSION) !== false) {
return;
}
preg_match_all('/unix:(.*?.sock)/m', $content, $matchCount);
@@ -317,22 +282,22 @@ private function updateNginxConfigFiles(string $version): void
}
$content = preg_replace(
'/unix:(.*?.sock)/m',
- 'unix:'.VALET_HOME_PATH.'/'.$this->socketFileName($version),
+ 'unix:' . VALET_HOME_PATH . '/' . $this->socketFileName($version),
$content
);
- $this->files->put(VALET_HOME_PATH.'/Nginx/'.$file, $content);
+ $this->files->put(VALET_HOME_PATH . '/Nginx/' . $file, $content);
});
//Action 2: Update NGINX valet.conf for php socket version.
- NginxFacade::installServer($version);
+ $this->nginx->installServer($version);
}
private function installExtensions(string $version): void
{
$extArray = [];
- $extensionPrefix = $this->getExtensionPrefix($version);
+ $extensionPrefix = $this->pm->getPhpExtensionPrefix($version);
foreach (self::COMMON_EXTENSIONS as $ext) {
- $extArray[] = "{$extensionPrefix}-{$ext}";
+ $extArray[] = "{$extensionPrefix}{$ext}";
}
$this->pm->ensureInstalled(implode(' ', $extArray));
}
@@ -342,43 +307,46 @@ private function installExtensions(string $version): void
*/
private function installConfiguration(string $version): void
{
- $contents = $this->files->get(__DIR__.'/../../stubs/fpm.conf');
+ $contents = $this->files->get(VALET_ROOT_PATH . '/cli/stubs/fpm.conf');
$contents = strArrayReplace([
'VALET_USER' => user(),
'VALET_GROUP' => group(),
'VALET_FPM_SOCKET_FILE' => $this->fpmSocketFile($version),
], $contents);
- $this->files->putAsUser($this->fpmConfigPath($version).'/'.self::FPM_CONFIG_FILE_NAME, $contents);
+ $this->files->putAsUser($this->fpmConfigPath($version) . '/' . self::FPM_CONFIG_FILE_NAME, $contents);
}
/**
* Get a list including the global PHP version and all PHP versions currently serving "isolated sites" (sites with
* custom Nginx configs pointing them to a specific PHP version).
+ * @return array
*/
private function utilizedPhpVersions(): array
{
- $fpmSockFiles = collect(self::SUPPORTED_PHP_VERSIONS)->map(function ($version) {
+ /** @var array $fpmSockFiles */
+ $fpmSockFiles = collect(self::ISOLATION_SUPPORTED_PHP_VERSIONS)->map(function ($version) {
return $this->socketFileName($this->normalizePhpVersion($version));
})->unique();
- $versions = NginxFacade::configuredSites()->map(function ($file) use ($fpmSockFiles) {
- $content = $this->files->get(VALET_HOME_PATH.'/Nginx/'.$file);
+ $versions = $this->nginx->configuredSites()->map(function ($file) use ($fpmSockFiles) {
+ $content = $this->files->get(VALET_HOME_PATH . '/Nginx/' . $file);
// Get the normalized PHP version for this config file, if it's defined
foreach ($fpmSockFiles as $sock) {
if (strpos($content, $sock) !== false) {
- // Extract the PHP version number from a custom .sock path and normalize it to, e.g., "php@7.4"
+ // Extract the PHP version number from a custom .sock path and normalize it, e.g. "7.4"
return $this->normalizePhpVersion(str_replace(['valet', '.sock'], '', $sock));
}
}
})->filter()->unique()->values()->toArray();
// Adding Default version in utilized versions list.
- if (!in_array($this->getCurrentVersion(), $versions)) {
- $versions[] = $this->getCurrentVersion();
+ if (!in_array($currentVersion = $this->getCurrentVersion(), $versions)) {
+ $versions[] = $currentVersion;
}
+ /** @var array $versions */
return $versions;
}
@@ -390,11 +358,12 @@ private function fpmConfigPath(string $version = null): string
$version = $version ?: $this->getCurrentVersion();
$versionWithoutDot = preg_replace('~[^\d]~', '', $version);
+ /** @var string $confDir */
return collect([
- '/etc/php/'.$version.'/fpm/pool.d', // Ubuntu
- '/etc/php'.$version.'/fpm/pool.d', // Ubuntu
- '/etc/php'.$version.'/php-fpm.d', // Manjaro
- '/etc/php'.$versionWithoutDot.'/php-fpm.d', // ArchLinux
+ '/etc/php/' . $version . '/fpm/pool.d', // Ubuntu
+ '/etc/php' . $version . '/fpm/pool.d', // Ubuntu
+ '/etc/php' . $version . '/php-fpm.d', // Manjaro
+ '/etc/php' . $versionWithoutDot . '/php-fpm.d', // ArchLinux
'/etc/php7/fpm/php-fpm.d', // openSUSE PHP7
'/etc/php8/fpm/php-fpm.d', // openSUSE PHP8
'/etc/php8/fpm/php-fpm.d', // openSUSE PHP8
@@ -402,21 +371,24 @@ private function fpmConfigPath(string $version = null): string
'/etc/php-fpm.d', // Fedora
'/etc/php/php-fpm.d', // Arch
])->first(function ($path) {
- return is_dir($path);
+ return $this->files->isDir($path);
}, function () {
throw new \DomainException('Unable to determine PHP-FPM configuration folder.');
});
}
/**
- * Validate PHP version.
- * @throws VersionException
+ * Validate PHP version for isolation process.
*/
- private function validateVersion(string $version): void
+ private function validateIsolationVersion(string $version): void
{
- if (!in_array($version, self::SUPPORTED_PHP_VERSIONS)) {
- throw new VersionException(
- "Invalid version [$version] used. Supported versions are :".implode(self::SUPPORTED_PHP_VERSIONS)
+ if (!in_array($version, self::ISOLATION_SUPPORTED_PHP_VERSIONS)) {
+ throw new \DomainException(
+ \sprintf(
+ "Invalid version [%s] used. Supported versions are: %s",
+ $version,
+ implode(', ', self::ISOLATION_SUPPORTED_PHP_VERSIONS)
+ )
);
}
}
@@ -426,27 +398,6 @@ private function validateVersion(string $version): void
*/
private function getDefaultVersion(): string
{
- return $this->normalizePhpVersion(PHP_VERSION);
- }
-
- private function getExtensionPrefix($version = null): string
- {
- $version = $version ?: $this->getCurrentVersion();
- return $this->pm->getPhpExtensionPrefix($version);
- }
-
- private function handlePackageUpdate($version): void
- {
- $installedPhpVersion = $this->config->get('installed_php_version');
- if ($installedPhpVersion && $installedPhpVersion >= $version) {
- if (is_dir(__DIR__.'/../../../vendor')) {
- // Local vendor
- $this->cli->runAsUser('composer update');
- } else {
- // Global vendor
- $this->cli->runAsUser('composer global require genesisweb/valet-linux-plus:'.VALET_VERSION.' -W');
- }
- $this->config->set('installed_php_version', $version);
- }
+ return $this->normalizePhpVersion(PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION);
}
}
diff --git a/cli/Valet/Request.php b/cli/Valet/Request.php
new file mode 100644
index 0000000..701d020
--- /dev/null
+++ b/cli/Valet/Request.php
@@ -0,0 +1,11 @@
+allowWildcardDnsDomains($httpHost),
- '.'.$this->config['tld']
+ '.'.$this->config['domain']
);
if (strpos($siteName, 'www.') === 0) {
@@ -204,11 +204,10 @@ public function sitePath(string $siteName): ?string
/**
* Return the default site path for uncaught URLs, if it's set.
- **/
+ */
public function defaultSitePath(): ?string
{
- if (
- isset($this->config['default'])
+ if (isset($this->config['default'])
&& is_string($this->config['default'])
&& is_dir($this->config['default'])
) {
diff --git a/cli/Valet/ServiceManagers/LinuxService.php b/cli/Valet/ServiceManagers/LinuxService.php
index 45a0fc5..92e76b3 100644
--- a/cli/Valet/ServiceManagers/LinuxService.php
+++ b/cli/Valet/ServiceManagers/LinuxService.php
@@ -2,12 +2,11 @@
namespace Valet\ServiceManagers;
+use ConsoleComponents\Writer;
use DomainException;
use Valet\CommandLine;
use Valet\Contracts\ServiceManager;
use Valet\Filesystem;
-use function Valet\info;
-use function Valet\warning;
class LinuxService implements ServiceManager
{
@@ -31,42 +30,45 @@ public function __construct(CommandLine $cli, Filesystem $files)
/**
* Start the given services.
- * @param array|string $services Service name
+ * @param string|string[]|null $services Service name
*/
- public function start($services): void
+ public function start(array|string|null $services): void
{
+ /** @var string[] $services */
$services = is_array($services) ? $services : func_get_args();
foreach ($services as $service) {
- info("Starting $service...");
+ Writer::twoColumnDetail(ucfirst($service), 'Starting');
$this->cli->quietly('sudo service '.$this->getRealService($service).' start');
}
}
/**
* Stop the given services.
- * @param array|string $services Service name
+ * @param string|string[]|null $services Service name
*/
- public function stop($services): void
+ public function stop(array|string|null $services): void
{
+ /** @var string[] $services */
$services = is_array($services) ? $services : func_get_args();
foreach ($services as $service) {
- info("Stopping $service...");
+ Writer::twoColumnDetail(ucfirst($service), 'Stopping');
$this->cli->quietly('sudo service '.$this->getRealService($service).' stop');
}
}
/**
* Restart the given services.
- * @param array|string $services Service name
+ * @param string|string[]|null $services Service name
*/
- public function restart($services): void
+ public function restart(array|string|null $services): void
{
+ /** @var string[] $services */
$services = is_array($services) ? $services : func_get_args();
foreach ($services as $service) {
- info("Restarting $service...");
+ Writer::twoColumnDetail(ucfirst($service), 'Restarting');
$this->cli->quietly('sudo service '.$this->getRealService($service).' restart');
}
}
@@ -76,7 +78,14 @@ public function restart($services): void
*/
public function printStatus(string $service): void
{
- info($this->cli->run('service '.$this->getRealService($service)));
+ $status = $this->cli->run('service '.$this->getRealService($service). ' status');
+ $running = strpos(trim($status), 'running');
+
+ if ($running) {
+ Writer::info(ucfirst($service).' is running...');
+ } else {
+ Writer::warn(ucfirst($service).' is stopped...');
+ }
}
/**
@@ -85,8 +94,8 @@ public function printStatus(string $service): void
public function disabled(string $service): bool
{
$service = $this->getRealService($service);
-
- return strpos(trim($this->cli->run("systemctl is-enabled {$service}")), 'enabled') === false;
+ // TODO: Do not use systemctl and stop using linux service class if systemd is available on all minimum versions
+ return !str_contains(trim($this->cli->run("systemctl is-enabled {$service}")), 'enabled');
}
/**
@@ -98,26 +107,24 @@ public function disable(string $service): void
$service = $this->getRealService($service);
$this->cli->quietly("sudo chmod -x /etc/init.d/{$service}");
$this->cli->quietly("sudo update-rc.d $service defaults");
+
+ Writer::twoColumnDetail(ucfirst($service), 'Disabled');
} catch (DomainException $e) {
- warning(ucfirst($service).' not available.');
+ Writer::warn(ucfirst($service).' not available.');
}
}
/**
* Enable services.
- *
- * @param mixed $services Service or services to enable
- *
- * @return void
*/
public function enable(string $service): void
{
try {
$service = $this->getRealService($service);
$this->cli->quietly("sudo update-rc.d $service defaults");
- info(ucfirst($service).' has been enabled');
+ Writer::twoColumnDetail(ucfirst($service), 'Enabled');
} catch (DomainException $e) {
- warning(ucfirst($service).' not available.');
+ Writer::warn(ucfirst($service).' unavailable.');
}
}
@@ -148,7 +155,7 @@ public function removeValetDns(): void
$servicePath = '/etc/init.d/valet-dns';
if ($this->files->exists($servicePath)) {
- info('Removing Valet DNS service...');
+ Writer::info('Removing Valet DNS service...');
$this->disable('valet-dns');
$this->stop('valet-dns');
$this->files->remove($servicePath);
diff --git a/cli/Valet/ServiceManagers/Systemd.php b/cli/Valet/ServiceManagers/Systemd.php
index 5de5d41..8554a99 100644
--- a/cli/Valet/ServiceManagers/Systemd.php
+++ b/cli/Valet/ServiceManagers/Systemd.php
@@ -2,12 +2,11 @@
namespace Valet\ServiceManagers;
+use ConsoleComponents\Writer;
use DomainException;
use Valet\CommandLine;
use Valet\Contracts\ServiceManager;
use Valet\Filesystem;
-use function Valet\info;
-use function Valet\warning;
class Systemd implements ServiceManager
{
@@ -31,42 +30,45 @@ public function __construct(CommandLine $cli, Filesystem $files)
/**
* Start the given services.
- * @param array|string $services Service name
+ * @param string|string[]|null $services Service name
*/
- public function start($services): void
+ public function start(array|string|null $services): void
{
+ /** @var string[] $services */
$services = is_array($services) ? $services : func_get_args();
foreach ($services as $service) {
- info("Starting $service...");
+ Writer::twoColumnDetail(ucfirst($service), 'Starting');
$this->cli->quietly('sudo systemctl start '.$this->getRealService($service));
}
}
/**
* Stop the given services.
- * @param array|string $services Service name
+ * @param string|string[]|null $services Service name
*/
- public function stop($services): void
+ public function stop(array|string|null $services): void
{
+ /** @var string[] $services */
$services = is_array($services) ? $services : func_get_args();
foreach ($services as $service) {
- info("Stopping $service...");
+ Writer::twoColumnDetail(ucfirst($service), 'Stopping');
$this->cli->quietly('sudo systemctl stop '.$this->getRealService($service));
}
}
/**
* Restart the given services.
- * @param array|string $services Service name
+ * @param string|string[]|null $services Service name
*/
- public function restart($services): void
+ public function restart(array|string|null $services): void
{
+ /** @var string[] $services */
$services = is_array($services) ? $services : func_get_args();
foreach ($services as $service) {
- info("Restarting $service...");
+ Writer::twoColumnDetail(ucfirst($service), 'Restarting');
$this->cli->quietly('sudo systemctl restart '.$this->getRealService($service));
}
}
@@ -80,9 +82,9 @@ public function printStatus(string $service): void
$running = strpos(trim($status), 'running');
if ($running) {
- info(ucfirst($service).' is running...');
+ Writer::info(ucfirst($service).' is running...');
} else {
- warning(ucfirst($service).' is stopped...');
+ Writer::warn(ucfirst($service).' is stopped...');
}
}
@@ -93,7 +95,7 @@ public function disabled(string $service): bool
{
$service = $this->getRealService($service);
- return strpos(trim($this->cli->run("systemctl is-enabled {$service}")), 'enabled') === false;
+ return !str_contains(trim($this->cli->run(\sprintf('systemctl is-enabled %s', $service))), 'enabled');
}
/**
@@ -106,12 +108,11 @@ public function enable(string $service): void
if ($this->disabled($service)) {
$this->cli->quietly('sudo systemctl enable '.$service);
- info(ucfirst($service).' has been enabled');
}
- info(ucfirst($service).' was already enabled');
+ Writer::twoColumnDetail(ucfirst($service), 'Enabled');
} catch (DomainException $e) {
- warning(ucfirst($service).' unavailable.');
+ Writer::warn(ucfirst($service).' unavailable.');
}
}
@@ -125,12 +126,11 @@ public function disable(string $service): void
if (!$this->disabled($service)) {
$this->cli->quietly('sudo systemctl disable '.$service);
- info(ucfirst($service).' has been disabled');
}
- info(ucfirst($service).' was already disabled');
+ Writer::twoColumnDetail(ucfirst($service), 'Disabled');
} catch (DomainException $e) {
- warning(ucfirst($service).' unavailable.');
+ Writer::warn(ucfirst($service).' unavailable.');
}
}
@@ -154,13 +154,13 @@ function () {
}
/**
- * Install Valet DNS services.
+ * Remove Valet DNS services.
*/
public function removeValetDns(): void
{
$servicePath = '/etc/systemd/system/valet-dns.service';
if ($this->files->exists($servicePath)) {
- info('Removing Valet DNS service...');
+ Writer::info('Removing Valet DNS service...');
$this->disable('valet-dns');
$this->stop('valet-dns');
$this->files->remove($servicePath);
@@ -174,13 +174,13 @@ public function isSystemd(): bool
/**
* Determine real service name.
- * TODO: Validate this function
+ * @throws DomainException
*/
private function getRealService(string $service): string
{
return collect($service)->first(
function ($service) {
- return strpos($this->cli->run("systemctl status {$service} | grep Loaded"), 'Loaded: loaded');
+ return strpos($this->cli->run("systemctl status $service | grep Loaded"), 'Loaded: loaded');
},
function () {
throw new DomainException('Unable to determine service name.');
diff --git a/cli/Valet/Site.php b/cli/Valet/Site.php
index 2b370ea..268c0cc 100644
--- a/cli/Valet/Site.php
+++ b/cli/Valet/Site.php
@@ -2,202 +2,26 @@
namespace Valet;
-use Tightenco\Collect\Support\Collection;
+use Illuminate\Support\Collection;
use Valet\Facades\PhpFpm as PhpFpmFacade;
+use Valet\Traits\Paths;
class Site
{
- /**
- * @var Configuration
- */
- public $config;
- /**
- * @var CommandLine
- */
- public $cli;
- /**
- * @var Filesystem
- */
- public $files;
+ use Paths;
+
+ public Configuration $config;
+ public CommandLine $cli;
+ public Filesystem $files;
/**
* Create a new Site instance.
- *
- * @param Configuration $config
- * @param CommandLine $cli
- * @param Filesystem $files
*/
public function __construct(Configuration $config, CommandLine $cli, Filesystem $files)
{
+ $this->config = $config;
$this->cli = $cli;
$this->files = $files;
- $this->config = $config;
- }
-
- /**
- * Get the real hostname for the given path, checking links.
- */
- public function host(string $path): string
- {
- foreach ($this->files->scandir($this->sitesPath()) as $link) {
- if (realpath($this->sitesPath().'/'.$link) === $path) {
- return $link;
- }
- }
-
- return basename($path);
- }
-
- /**
- * Link the current working directory with the given name.
- */
- public function link(string $target, string $link): string
- {
- $this->files->ensureDirExists(
- $linkPath = $this->sitesPath(),
- user()
- );
-
- $this->config->prependPath($linkPath);
-
- $this->files->symlinkAsUser($target, $linkPath.'/'.$link);
-
- return $linkPath.'/'.$link;
- }
-
- /**
- * Pretty print out all links in Valet.
- */
- public function links(): Collection
- {
- $certsPath = $this->certificatesPath();
-
- $this->files->ensureDirExists($certsPath, user());
-
- $certs = $this->getCertificates($certsPath);
-
- return $this->getLinks($this->sitesPath(), $certs);
- }
-
- /**
- * Get all sites which are proxies (not Links, and contain proxy_pass directive).
- */
- public function proxies(): Collection
- {
- $dir = $this->nginxPath();
- $domain = $this->config->read()['domain'];
- $links = $this->links();
- $certs = $this->getCertificates($this->certificatesPath());
- if (!$this->files->exists($dir)) {
- return collect();
- }
-
- return collect($this->files->scandir($dir))
- ->filter(function ($site) use ($domain) {
- // keep sites that match our TLD
-
- return endsWith($site, '.'.$domain);
- })->map(function ($site) use ($domain) {
- // remove the TLD suffix for consistency
- return str_replace('.'.$domain, '', $site);
- })->reject(function ($site) use ($links) {
- return $links->has($site);
- })->mapWithKeys(function ($site) {
- $host = $this->getProxyHostForSite($site) ?: '(other)';
-
- return [$site => $host];
- })->reject(function ($host) {
- // If proxy host is null, it may be just a normal SSL stub, or something else;
- // either way we exclude it from the list
- return $host === '(other)';
- })->map(function ($host, $site) use ($certs, $domain) {
- $secured = $certs->has($site);
- $url = ($secured ? 'https' : 'http').'://'.$site.'.'.$domain;
-
- return [
- 'url' => $url,
- 'secured' => $secured ? ' X' : '',
- 'path' => $host,
- ];
- });
- }
-
- /**
- * Unsecure the given URL so that it will use HTTP again.
- */
- public function proxyDelete(string $url): void
- {
- $tld = $this->config->read()['domain'];
- if (!endsWith($url, '.'.$tld)) {
- $url .= '.'.$tld;
- }
-
- $this->unsecure($url);
- $this->files->unlink($this->nginxPath($url));
-
- info('Valet will no longer proxy [https://'.$url.'].');
- }
-
- /**
- * Build the Nginx proxy config for the specified domain.
- * @throws \InvalidArgumentException
- */
- public function proxyCreate(string $url, string $host, bool $secure = false): void
- {
- if (!preg_match('~^https?://.*$~', $host)) {
- throw new \InvalidArgumentException(sprintf('"%s" is not a valid URL', $host));
- }
-
- $domain = $this->config->read()['domain'];
-
- if (!endsWith($url, '.'.$domain)) {
- $url .= '.'.$domain;
- }
-
- $siteConf = $this->files->get(
- $secure ? __DIR__.'/../stubs/secure.proxy.valet.conf' : __DIR__.'/../stubs/proxy.valet.conf'
- );
-
- // General Variables
- $siteConf = strArrayReplace(
- [
- 'VALET_HOME_PATH' => $this->valetHomePath(),
- 'VALET_SERVER_PATH' => VALET_SERVER_PATH,
- 'VALET_STATIC_PREFIX' => VALET_STATIC_PREFIX,
- 'VALET_SITE' => $url,
- 'VALET_HTTP_PORT' => $this->config->get('port', 80),
- 'VALET_HTTPS_PORT' => $this->config->get('https_port', 443),
- ],
- $siteConf
- );
-
- // Proxy specific variables
- $siteConf = strArrayReplace(
- [
- 'VALET_PROXY_HOST' => $host,
- ],
- $siteConf
- );
-
- if ($secure) {
- $this->secure($url, $siteConf);
- } else {
- $this->put($url, $siteConf);
- }
-
- $protocol = $secure ? 'https' : 'http';
-
- info('Valet will now proxy ['.$protocol.'://'.$url.'] traffic to ['.$host.'].');
- }
-
- /**
- * Unlink the given symbolic link.
- */
- public function unlink(string $name): void
- {
- if ($this->files->exists($path = $this->sitesPath().'/'.$name)) {
- $this->files->unlink($path);
- }
}
/**
@@ -210,178 +34,26 @@ public function pruneLinks(): void
$this->files->removeBrokenLinksAt($this->sitesPath());
}
- /**
- * Re-secure all currently secured sites with a fresh domain.
- *
- * @param string $oldDomain
- * @param string $domain
- *
- * @return void
- */
- public function resecureForNewDomain(string $oldDomain, string $domain): void
- {
- if (!$this->files->exists($this->certificatesPath())) {
- return;
- }
-
- $secured = $this->secured();
-
- foreach ($secured as $oldUrl) {
- $newUrl = str_replace('.'.$oldDomain, '.'.$domain, $oldUrl);
- $nginxConf = $this->getNginxConf($oldUrl);
- if ($nginxConf) {
- $nginxConf = str_replace($oldUrl, $newUrl, $nginxConf);
- }
-
- $this->unsecure($oldUrl);
-
- $this->secure(
- $newUrl,
- $nginxConf
- );
- }
- }
-
- /**
- * Get all the URLs that are currently secured.
- */
- public function secured(): Collection
- {
- return collect($this->files->scandir($this->certificatesPath()))
- ->map(function ($file) {
- return str_replace(['.key', '.csr', '.crt', '.conf'], '', $file);
- })->unique()->values();
- }
-
- /**
- * Secure the given host with TLS.
- */
- public function secure(string $url, string $stub = null): void
- {
- if (is_null($stub)) {
- $stub = $this->prepareConf($url, true);
- }
- $this->unsecure($url);
-
- $this->files->ensureDirExists($this->certificatesPath(), user());
-
- $this->createCertificate($url);
-
- $this->createSecureNginxServer($url, $stub);
- }
-
- /**
- * Unsecure the given URL so that it will use HTTP again.
- */
- public function unsecure(string $url, bool $preserveUnsecureConfig = false): void
- {
- $stub = null;
- if ($this->files->exists($this->certificatesPath().'/'.$url.'.crt')) {
- if ($preserveUnsecureConfig) {
- $stub = $this->prepareConf($url);
- }
-
- $this->files->unlink($this->nginxPath().'/'.$url);
-
- $this->files->unlink($this->certificatesPath().'/'.$url.'.conf');
- $this->files->unlink($this->certificatesPath().'/'.$url.'.key');
- $this->files->unlink($this->certificatesPath().'/'.$url.'.csr');
- $this->files->unlink($this->certificatesPath().'/'.$url.'.crt');
-
- $this->cli->run(sprintf('certutil -d sql:$HOME/.pki/nssdb -D -n "%s"', $url));
- $this->cli->run(sprintf('certutil -d $HOME/.mozilla/firefox/*.default -D -n "%s"', $url));
- }
- if ($stub) {
- $this->put($url, $stub);
- }
- }
-
- /**
- * Regenerate all secured file configurations.
- */
- public function regenerateSecuredSitesConfig(): void
- {
- $this->secured()->each(function ($url) {
- $this->createSecureNginxServer($url);
- });
- }
-
/**
* Get the site URL from a directory if it's a valid Valet site.
+ * @throws \DomainException
*/
public function getSiteUrl(string $directory): string
{
- $tld = $this->config->read()['domain'];
+ $tld = $this->config->get('domain');
if ($directory == '.' || $directory == './') { // Allow user to use dot as current directory site `--site=.`
- $directory = $this->host(getcwd());
+ $directory = basename((string)getcwd());
}
- $directory = str_replace('.'.$tld, '', $directory); // Remove .tld from site name if it was provided
+ $directory = str_replace('.' . $tld, '', $directory); // Remove .tld from site name if it was provided
- if (!$this->parked()->merge($this->links())->where('site', $directory)->count() > 0) {
+ $servedSites = $this->servedSites();
+ if (!$servedSites->has($directory)) {
throw new \DomainException("The [{$directory}] site could not be found in Valet's site list.");
}
- return $directory.'.'.$tld;
- }
-
- /**
- * Create new nginx config or modify existing nginx config to isolate this site
- * to a custom version of PHP.
- */
- public function isolate(string $url, string $phpVersion, bool $secure = false): void
- {
- $stub = $secure ? __DIR__.'/../stubs/secure.isolated.valet.conf' : __DIR__.'/../stubs/isolated.valet.conf';
-
- // Isolate specific variables
- $siteConf = strArrayReplace([
- 'VALET_FPM_SOCKET_FILE' => PhpFpmFacade::fpmSocketFile($phpVersion),
- 'VALET_ISOLATED_PHP_VERSION' => $phpVersion,
- ], $this->files->get($stub));
-
- if ($secure) {
- $this->secure($url, $siteConf);
- } else {
- $this->put($url, $siteConf);
- }
- }
-
- /**
- * Remove PHP Version isolation from a specific site.
- */
- public function removeIsolation(string $valetSite): void
- {
- // If a site has an SSL certificate, we need to keep its custom config file, but we can
- // just re-generate it without defining a custom `valet.sock` file
- if ($this->files->exists($this->certificatesPath().'/'.$valetSite.'.crt')) {
- $this->createSecureNginxServer($valetSite);
- } else {
- // When site doesn't have SSL, we can remove the custom nginx config file to remove isolation
- $this->files->unlink($this->nginxPath($valetSite));
- }
- }
-
- /**
- * Extract PHP version of exising nginx config.
- */
- public function customPhpVersion(string $url, string $siteConf = null, bool $returnDecimal = false): ?string
- {
- if (is_null($siteConf) && !$this->files->exists($this->nginxPath($url))) {
- return null;
- }
-
- $siteConf = $siteConf ?: $this->files->get($this->nginxPath($url));
- if (strpos($siteConf, '# '.ISOLATED_PHP_VERSION) !== false) {
- preg_match('/^# ISOLATED_PHP_VERSION=(.*?)\n/m', $siteConf, $version);
- if ($returnDecimal) {
- return $version[1];
- }
-
- return preg_replace("/[^\d]*/", '', $version[1]); // Example output: "74" or "81"
- }
-
- return null;
+ return $directory . '.' . $tld;
}
/**
@@ -389,8 +61,10 @@ public function customPhpVersion(string $url, string $siteConf = null, bool $ret
*/
public function phpRcVersion(string $site): ?string
{
- if ($site = $this->parked()->merge($this->links())->where('site', $site)->first()) {
- $path = data_get($site, 'path').'/.valetphprc';
+ $servedSites = $this->servedSites();
+ if ($servedSites->has($site)) {
+ $sitePath = $servedSites->get($site);
+ $path = $sitePath . '/.valetphprc';
if ($this->files->exists($path)) {
return PhpFpmFacade::normalizePhpVersion(trim($this->files->get($path)));
@@ -400,410 +74,34 @@ public function phpRcVersion(string $site): ?string
}
/**
- * Extract Proxy pass of exising nginx config.
- */
- private function getProxyPass(string $url, string $siteConf = null): ?string
- {
- if (is_null($siteConf) && !$this->files->exists($this->nginxPath($url))) {
- return null;
- }
-
- $siteConf = $siteConf ?: $this->files->get($this->nginxPath($url));
- preg_match('/proxy_pass (?.*?);/m', $siteConf, $matches);
-
- return $matches['host'] ?? null;
- }
-
- private function getNginxConf(string $url): ?string
- {
- if (!$this->files->exists($this->nginxPath().'/'.$url)) {
- return null;
- }
-
- return $this->files->get($this->nginxPath().'/'.$url);
- }
-
- /**
- * Prepare Nginx Conf based on existing config file.
- **/
- private function prepareConf(string $url, bool $requireSecure = false): ?string
- {
- if (!$this->files->exists($this->nginxPath($url))) {
- return null;
- }
-
- $existingConf = $this->files->get($this->nginxPath($url));
-
- preg_match('/# valet stub: (?secure)?(?:\.)?(?.*?).valet.conf/m', $existingConf, $stubDetail);
-
- if (empty($stubDetail['stub'])) {
- return null;
- }
-
- if ($stubDetail['stub'] === 'proxy') {
- // Find proxy_pass from existingConf.
- $proxyPass = $this->getProxyPass($url, $existingConf);
- if (!$proxyPass) {
- return null;
- }
- $stub = $requireSecure ?
- __DIR__.'/../stubs/secure.proxy.valet.conf' :
- __DIR__.'/../stubs/proxy.valet.conf';
- $stub = $this->files->get($stub);
-
- return strArrayReplace([
- 'VALET_PROXY_HOST' => $proxyPass,
- ], $stub);
- }
-
- if ($stubDetail['stub'] === 'isolated') {
- $phpVersion = $this->customPhpVersion($url, $existingConf, true);
- // empty($stubDetail['tls']) || We can use this statement if needed.
- $stub = $requireSecure ?
- __DIR__.'/../stubs/secure.isolated.valet.conf' :
- __DIR__.'/../stubs/isolated.valet.conf';
- $stub = $this->files->get($stub);
- // Isolate specific variables
- return strArrayReplace([
- 'VALET_FPM_SOCKET_FILE' => PhpFpmFacade::fpmSocketFile($phpVersion),
- 'VALET_ISOLATED_PHP_VERSION' => $phpVersion,
- ], $stub);
- }
-
- return null;
- }
-
- /**
- * Identify whether a site is for a proxy by reading the host name from its config file.
- */
- private function getProxyHostForSite(string $site, string $configContents = null): ?string
- {
- $siteConf = $configContents ?: $this->getSiteConfigFileContents($site);
-
- if (empty($siteConf)) {
- return null;
- }
-
- $host = null;
- if (preg_match('~proxy_pass\s+(?https?://.*)\s*;~', $siteConf, $patterns)) {
- $host = trim($patterns['host']);
- }
-
- return $host;
- }
-
- /**
- * Get the path to Nginx site configuration files.
- */
- private function nginxPath(string $additionalPath = null): string
- {
- return $this->valetHomePath().'/Nginx'.($additionalPath ? '/'.$additionalPath : '');
- }
-
- public function valetHomePath(): string
- {
- return VALET_HOME_PATH;
- }
-
- /**
- * Create the given nginx host.
- */
- private function put(string $url, string $siteConf): void
- {
- $this->unsecure($url);
-
- $this->files->ensureDirExists($this->nginxPath(), user());
-
- $siteConf = strArrayReplace(
- [
- 'VALET_HOME_PATH' => $this->valetHomePath(),
- 'VALET_SERVER_PATH' => VALET_SERVER_PATH,
- 'VALET_STATIC_PREFIX' => VALET_STATIC_PREFIX,
- 'VALET_SITE' => $url,
- 'VALET_HTTP_PORT' => $this->config->get('port', 80),
- 'VALET_HTTPS_PORT' => $this->config->get('https_port', 443),
- ],
- $siteConf
- );
-
- $this->files->putAsUser(
- $this->nginxPath($url),
- $siteConf
- );
- }
-
- private function getSiteConfigFileContents(string $site, string $suffix = null): ?string
- {
- $config = $this->config->read();
- $suffix = $suffix ?: '.'.$config['domain'];
- $file = str_replace($suffix, '', $site).$suffix;
-
- return $this->files->exists($this->nginxPath($file)) ? $this->files->get($this->nginxPath($file)) : null;
- }
-
- /**
- * Get all certificates from config folder.
- */
- private function getCertificates(string $path = null): Collection
- {
- $path = $path ?: $this->certificatesPath();
-
- return collect($this->files->scanDir($path))->filter(function ($value) {
- return ends_with($value, '.crt');
- })->map(function ($cert) {
- return substr($cert, 0, -9);
- })->flip();
- }
-
- /**
- * Get list of links and present them formatted.
- */
- private function getLinks(string $path, Collection $certs): Collection
- {
- $config = $this->config->read();
-
- $httpPort = $this->httpSuffix();
- $httpsPort = $this->httpsSuffix();
-
- return collect($this->files->scanDir($path))->mapWithKeys(function ($site) use ($path) {
- return [$site => $this->files->readLink($path.'/'.$site)];
- })->map(function ($path, $site) use ($certs, $config, $httpPort, $httpsPort) {
- $secured = $certs->has($site);
-
- $url = ($secured ? 'https' : 'http').'://'.$site.'.'.$config['domain'].($secured ? $httpsPort : $httpPort);
- $phpVersion = $this->getPhpVersion($site.'.'.$config['domain']);
-
- return [
- 'site' => $site,
- 'secured' => $secured ? ' X' : '',
- 'url' => $url,
- 'path' => $path,
- 'phpVersion' => $phpVersion,
- ];
- });
- }
-
- /**
- * Return http port suffix.
- */
- private function httpSuffix(): string
- {
- $port = $this->config->get('port', 80);
-
- return ($port == 80) ? '' : ':'.$port;
- }
-
- /**
- * Return https port suffix.
+ * List of all sites served by valet
+ * @return Collection
*/
- private function httpsSuffix(): string
+ private function servedSites(): Collection
{
- $port = $this->config->get('https_port', 443);
+ $parkedSites = [];
- return ($port == 443) ? '' : ':'.$port;
- }
-
- /**
- * Create and trust a certificate for the given URL.
- */
- private function createCertificate(string $url): void
- {
- $keyPath = $this->certificatesPath().'/'.$url.'.key';
- $csrPath = $this->certificatesPath().'/'.$url.'.csr';
- $crtPath = $this->certificatesPath().'/'.$url.'.crt';
- $confPath = $this->certificatesPath().'/'.$url.'.conf';
-
- $this->buildCertificateConf($confPath, $url);
- $this->createPrivateKey($keyPath);
- $this->createSigningRequest($url, $keyPath, $csrPath, $confPath);
-
- $this->cli->runAsUser(sprintf(
- 'openssl x509 -req -sha256 -days 365 -in %s -signkey %s -out %s -extensions v3_req -extfile %s',
- $csrPath,
- $keyPath,
- $crtPath,
- $confPath
- ));
-
- $this->trustCertificate($crtPath, $url);
- }
-
- /**
- * Create the private key for the TLS certificate.
- */
- private function createPrivateKey(string $keyPath): void
- {
- $this->cli->runAsUser(sprintf('openssl genrsa -out %s 2048', $keyPath));
- }
-
- /**
- * Create the signing request for the TLS certificate.
- */
- private function createSigningRequest(string $url, string $keyPath, string $csrPath, string $confPath): void
- {
- $this->cli->runAsUser(sprintf(
- 'openssl req -new -key %s -out %s -subj "/C=US/ST=MN/O=Valet/localityName=Valet/commonName=%s/organizationalUnitName=Valet/emailAddress=valet/" -config %s -passin pass:',
- $keyPath,
- $csrPath,
- $url,
- $confPath
- ));
- }
-
- /**
- * Build the SSL config for the given URL.
- */
- private function buildCertificateConf(string $path, string $url): void
- {
- $config = str_replace('VALET_DOMAIN', $url, $this->files->get(__DIR__.'/../stubs/openssl.conf'));
- $this->files->putAsUser($path, $config);
- }
-
- /**
- * Trust the given certificate file in the Mac Keychain.
- */
- private function trustCertificate(string $crtPath, string $url): void
- {
- $this->cli->run(sprintf(
- 'certutil -d sql:$HOME/.pki/nssdb -A -t TC -n "%s" -i "%s"',
- $url,
- $crtPath
- ));
-
- $this->cli->run(sprintf(
- 'certutil -d $HOME/.mozilla/firefox/*.default -A -t TC -n "%s" -i "%s"',
- $url,
- $crtPath
- ));
-
- $this->cli->run(sprintf(
- 'certutil -d $HOME/snap/firefox/common/.mozilla/firefox/*.default -A -t TC -n "%s" -i "%s"',
- $url,
- $crtPath
- ));
- }
-
- private function createSecureNginxServer(string $url, string $stub = null): void
- {
- $this->files->putAsUser(
- $this->nginxPath($url),
- $this->buildSecureNginxServer($url, $stub)
- );
- }
-
- /**
- * Build the TLS secured Nginx server for the given URL.
- */
- private function buildSecureNginxServer(string $url, ?string $stub = null): string
- {
- $stub = ($stub ?: $this->files->get(__DIR__.'/../stubs/secure.valet.conf'));
- $path = $this->certificatesPath();
-
- return strArrayReplace(
- [
- 'VALET_HOME_PATH' => $this->valetHomePath(),
- 'VALET_SERVER_PATH' => VALET_SERVER_PATH,
- 'VALET_STATIC_PREFIX' => VALET_STATIC_PREFIX,
- 'VALET_SITE' => $url,
- 'VALET_CERT' => $path.'/'.$url.'.crt',
- 'VALET_KEY' => $path.'/'.$url.'.key',
- 'VALET_HTTP_PORT' => $this->config->get('port', 80),
- 'VALET_HTTPS_PORT' => $this->config->get('https_port', 443),
- 'VALET_REDIRECT_PORT' => $this->httpsSuffix(),
- 'VALET_FPM_SOCKET_FILE' => PhpFpmFacade::fpmSocketFile(PhpFpmFacade::getCurrentVersion()),
- ],
- $stub
- );
- }
-
- /**
- * Get the path to the linked Valet sites.
- */
- private function sitesPath(): string
- {
- return $this->valetHomePath().'/Sites';
- }
-
- /**
- * Get the path to the Valet TLS certificates.
- */
- private function certificatesPath(): string
- {
- return $this->valetHomePath().'/Certificates';
- }
-
- /**
- * Get list of sites and return them formatted
- * Will work for symlink and normal site paths.
- */
- private function getSites(string $path, Collection $certs): Collection
- {
- $config = $this->config->read();
-
- $this->files->ensureDirExists($path, user());
-
- return collect($this->files->scandir($path))->mapWithKeys(function ($site) use ($path) {
- $sitePath = $path.'/'.$site;
-
- if ($this->files->isLink($sitePath)) {
- $realPath = $this->files->readLink($sitePath);
- } else {
- $realPath = $this->files->realpath($sitePath);
- }
-
- return [$site => $realPath];
- })->filter(function ($path) {
- return $this->files->isDir($path);
- })->map(function ($path, $site) use ($certs, $config) {
- $secured = $certs->has($site);
- $url = ($secured ? 'https' : 'http').'://'.$site.'.'.$config['domain'];
- $phpVersion = $this->getPhpVersion($site.'.'.$config['domain']);
-
- return [
- 'site' => $site,
- 'secured' => $secured ? ' X' : '',
- 'url' => $url,
- 'path' => $path,
- 'phpVersion' => $phpVersion,
- ];
- });
- }
-
- /**
- * Get the PHP version for the given site.
- */
- private function getPhpVersion(string $url): string
- {
- $defaultPhpVersion = PhpFpmFacade::getCurrentVersion();
- $phpVersion = PhpFpmFacade::normalizePhpVersion($this->customPhpVersion($url));
- if (empty($phpVersion)) {
- $phpVersion = PhpFpmFacade::normalizePhpVersion($defaultPhpVersion);
- }
-
- return $phpVersion;
- }
-
- private function parked(): Collection
- {
- $certs = $this->getCertificates();
-
- $links = $this->getSites($this->sitesPath(), $certs);
-
- $config = $this->config->read();
- $parkedLinks = collect();
- foreach (array_reverse($config['paths']) as $path) {
+ /** @var array $parkedPaths */
+ $parkedPaths = $this->config->get('paths', []);
+ foreach ($parkedPaths as $path) {
if ($path === $this->sitesPath()) {
continue;
}
- // 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);
- });
+ $sites = $this->files->scandir($path);
+ foreach ($sites as $site) {
+ if ($this->files->isDir($path . '/' . $site)) {
+ $parkedSites[$site] = $path . '/' . $site;
+ }
+ }
+ }
- $parkedLinks = $parkedLinks->merge($sites);
+ // Get sites from links
+ $linkedSites = $this->files->scandir($this->sitesPath());
+ foreach ($linkedSites as $linkedSite) {
+ $parkedSites[$linkedSite] = $this->files->realpath($this->sitesPath($linkedSite));
}
- return $parkedLinks;
+ return collect($parkedSites);
}
}
diff --git a/cli/Valet/SiteIsolate.php b/cli/Valet/SiteIsolate.php
new file mode 100644
index 0000000..7b8d8c7
--- /dev/null
+++ b/cli/Valet/SiteIsolate.php
@@ -0,0 +1,232 @@
+pm = $pm;
+ $this->config = $config;
+ $this->files = $filesystem;
+ $this->siteSecure = $siteSecure;
+ $this->site = $site;
+ }
+
+ /**
+ * Isolate a given directory to use a specific version of PHP.
+ */
+ public function isolateDirectory(string $directory, string $version, bool $secure = false): bool
+ {
+ try {
+ $site = $this->site->getSiteUrl($directory);
+
+ $version = PhpFpmFacade::normalizePhpVersion($version);
+ $this->validateIsolationVersion($version);
+
+ $fpmName = $this->pm->getPhpFpmName($version);
+ if (!$this->pm->installed($fpmName)) {
+ PhpFpmFacade::install($version);
+ }
+
+ $oldCustomPhpVersion = $this->isolatedPhpVersion($site);
+
+ $this->isolate($site, $version, $secure);
+
+ if ($oldCustomPhpVersion) {
+ PhpFpmFacade::stopIfUnused($oldCustomPhpVersion);
+ }
+
+ PhpFpmFacade::restart($version);
+ NginxFacade::restart();
+
+ $this->addBinFileToConfig($version, $directory);
+ } catch (DomainException $exception) {
+ Writer::error($exception->getMessage());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove PHP version isolation for a given directory.
+ */
+ public function unIsolateDirectory(string $directory): void
+ {
+ $site = $this->site->getSiteUrl($directory);
+
+ $oldCustomPhpVersion = $this->isolatedPhpVersion($site);
+
+ $this->removeIsolation($site);
+
+ if ($oldCustomPhpVersion) {
+ PhpFpmFacade::stopIfUnused($oldCustomPhpVersion);
+ }
+ NginxFacade::restart();
+
+ $this->removeBinFromConfig($directory);
+ }
+
+ /**
+ * List isolated directories with version.
+ */
+ public function isolatedDirectories(): Collection
+ {
+ $securedSites = $this->siteSecure->secured();
+
+ return NginxFacade::configuredSites()->filter(function ($item) {
+ return str_contains($this->files->get($this->nginxPath($item)), ISOLATED_PHP_VERSION);
+ })->map(function ($site) use ($securedSites) {
+ $secured = $securedSites->contains($site);
+
+ $url = \sprintf(
+ '%s://%s',
+ $secured ? 'https' : 'http',
+ $site
+ );
+
+ return [
+ 'url' => $url,
+ 'secured' => $secured ? '✓' : '✕',
+ 'version' => PhpFpmFacade::normalizePhpVersion((string)$this->isolatedPhpVersion($site))
+ ];
+ });
+ }
+
+ /**
+ * Extract PHP version of exising nginx config.
+ */
+ public function isolatedPhpVersion(string $url): ?string
+ {
+ if (!$this->files->exists($this->nginxPath($url))) {
+ return null;
+ }
+
+ $siteConf = $this->files->get($this->nginxPath($url));
+ if (strpos($siteConf, '# ' . ISOLATED_PHP_VERSION) !== false) {
+ preg_match('/^# ISOLATED_PHP_VERSION=(.*?)\n/m', $siteConf, $version);
+
+ return $version[1];
+ }
+
+ return null;
+ }
+
+ /**
+ * Create new nginx config or modify existing nginx config to isolate this site
+ * to a custom version of PHP.
+ */
+ private function isolate(string $url, string $phpVersion, bool $secure = false): void
+ {
+ $stub = $secure ?
+ VALET_ROOT_PATH . '/cli/stubs/secure.isolated.valet.conf'
+ : VALET_ROOT_PATH . '/cli/stubs/isolated.valet.conf';
+
+ // Isolate specific variables
+ $siteConf = strArrayReplace([
+ 'VALET_FPM_SOCKET_FILE' => PhpFpmFacade::fpmSocketFile($phpVersion),
+ 'VALET_ISOLATED_PHP_VERSION' => $phpVersion,
+ ], $this->files->get($stub));
+
+ if ($secure) {
+ $this->siteSecure->secure($url, $siteConf);
+ } else {
+ $siteConf = $this->siteSecure->buildUnsecureNginxServer($url, $siteConf);
+
+ $this->files->putAsUser(
+ $this->nginxPath($url),
+ $siteConf
+ );
+ }
+ }
+
+ /**
+ * Remove PHP Version isolation from a specific site.
+ */
+ private function removeIsolation(string $siteName): void
+ {
+ // If a site has an SSL certificate, we need to keep its custom config file, but we can
+ // just re-generate it without defining a custom `valet.sock` file
+ if ($this->files->exists($this->certificatesPath() . '/' . $siteName . '.crt')) {
+ $conf = $this->siteSecure->buildSecureNginxServer($siteName);
+ $this->files->putAsUser($this->nginxPath($siteName), $conf);
+
+ return;
+ }
+
+ // When site doesn't have SSL, we can remove the custom nginx config file to remove isolation
+ $this->files->unlink($this->nginxPath($siteName));
+ }
+
+ /**
+ * Validate PHP version for isolation process.
+ */
+ private function validateIsolationVersion(string $version): void
+ {
+ if (!in_array($version, PhpFpm::ISOLATION_SUPPORTED_PHP_VERSIONS)) {
+ throw new DomainException(
+ sprintf(
+ "Invalid version [%s] used. Supported versions are: %s",
+ $version,
+ implode(', ', PhpFpm::ISOLATION_SUPPORTED_PHP_VERSIONS)
+ )
+ );
+ }
+ }
+
+ private function addBinFileToConfig(string $version, string $directoryName): void
+ {
+ $directoryName = $this->removeTld($directoryName);
+ $binaryFile = DevToolsFacade::getBin('php' . $version, ['/usr/local/bin/php']);
+ /** @var array $isolatedConfig */
+ $isolatedConfig = $this->config->get('isolated_versions', []);
+
+ $isolatedConfig[$directoryName] = $binaryFile;
+ $this->config->set('isolated_versions', $isolatedConfig);
+ }
+
+ private function removeBinFromConfig(string $directoryName): void
+ {
+ $directoryName = $this->removeTld($directoryName);
+ /** @var array $isolatedConfig */
+ $isolatedConfig = $this->config->get('isolated_versions', []);
+ if (isset($isolatedConfig[$directoryName])) {
+ unset($isolatedConfig[$directoryName]);
+ $this->config->set('isolated_versions', $isolatedConfig);
+ }
+ }
+
+ private function removeTld(string $domainName): string
+ {
+ /** @var string $tld */
+ $tld = $this->config->get('domain');
+ if (str_ends_with($domainName, \sprintf('.%s', $tld))) {
+ $domainName = str_replace(\sprintf('.%s', $tld), '', $domainName);
+ }
+
+ return $domainName;
+ }
+}
diff --git a/cli/Valet/SiteLink.php b/cli/Valet/SiteLink.php
new file mode 100644
index 0000000..03715f8
--- /dev/null
+++ b/cli/Valet/SiteLink.php
@@ -0,0 +1,108 @@
+files = $filesystem;
+ $this->config = $config;
+ $this->siteSecure = $siteSecure;
+ }
+
+ /**
+ * Link the current working directory with the given name.
+ */
+ public function link(string $target, string $link): string
+ {
+ $linkPath = $this->sitesPath();
+ $this->files->ensureDirExists($linkPath, user());
+
+ $this->config->addPath($linkPath, true);
+
+ $this->files->symlinkAsUser($target, $linkPath . '/' . $link);
+
+ return $linkPath . '/' . $link;
+ }
+
+ /**
+ * Unlink the given symbolic link.
+ */
+ public function unlink(string $name): void
+ {
+ $path = $this->sitesPath() . '/' . $name;
+ if ($this->files->exists($path)) {
+ $this->files->unlink($path);
+ }
+ }
+
+ /**
+ * Pretty print out all links in Valet.
+ * @return Collection>
+ */
+ public function links(): Collection
+ {
+ $certsPath = $this->certificatesPath();
+ $path = $this->sitesPath();
+
+ $this->files->ensureDirExists($certsPath, user());
+
+ $securedSites = $this->siteSecure->secured();
+
+ /** @var string $domain */
+ $domain = $this->config->get('domain');
+
+ $httpPort = $this->httpSuffix();
+ $httpsPort = $this->httpsSuffix();
+
+ return collect($this->files->scandir($path))->mapWithKeys(function ($site) use ($path) {
+ return [$site => $this->files->readLink($path . '/' . $site)];
+ })->map(function ($path, $site) use ($securedSites, $domain, $httpPort, $httpsPort) {
+ $secured = $securedSites->contains($site . '.' . $domain);
+
+ $url = \sprintf(
+ '%s://%s.%s%s',
+ $secured ? 'https' : 'http',
+ $site,
+ $domain,
+ $secured ? $httpsPort : $httpPort
+ );
+
+ return [
+ 'url' => $url,
+ 'secured' => $secured ? '✓' : '✕',
+ 'path' => $path,
+ ];
+ });
+ }
+
+ /**
+ * Return http port suffix.
+ */
+ private function httpSuffix(): string
+ {
+ $port = $this->config->get('port', 80);
+
+ return ($port == 80) ? '' : ':' . $port;
+ }
+
+ /**
+ * Return https port suffix.
+ */
+ private function httpsSuffix(): string
+ {
+ $port = $this->config->get('https_port', 443);
+
+ return ($port == 443) ? '' : ':' . $port;
+ }
+}
diff --git a/cli/Valet/SiteProxy.php b/cli/Valet/SiteProxy.php
new file mode 100644
index 0000000..eb1aee2
--- /dev/null
+++ b/cli/Valet/SiteProxy.php
@@ -0,0 +1,124 @@
+files = $filesystem;
+ $this->config = $config;
+ $this->siteSecure = $siteSecure;
+ }
+
+ /**
+ * Build the Nginx proxy config for the specified domain.
+ * @throws \InvalidArgumentException
+ */
+ public function proxyCreate(string $url, string $host, bool $secure = false): void
+ {
+ if (!preg_match('~^https?://.*$~', $host)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid URL', $host));
+ }
+
+ $domain = $this->config->get('domain');
+
+ if (!str_ends_with($url, '.' . $domain)) {
+ $url .= '.' . $domain;
+ }
+
+ $siteConf = $this->files->get(
+ $secure
+ ? VALET_ROOT_PATH . '/cli/stubs/secure.proxy.valet.conf'
+ : VALET_ROOT_PATH . '/cli/stubs/proxy.valet.conf'
+ );
+
+ // Proxy specific variables
+ $siteConf = strArrayReplace(
+ [
+ 'VALET_PROXY_HOST' => $host,
+ ],
+ $siteConf
+ );
+
+ if ($secure) {
+ $this->siteSecure->secure($url, $siteConf);
+ } else {
+ $siteConf = $this->siteSecure->buildUnsecureNginxServer($url, $siteConf);
+
+ $this->files->putAsUser(
+ $this->nginxPath($url),
+ $siteConf
+ );
+ }
+ }
+
+ /**
+ * Get all sites which are proxies (not Links, and contain proxy_pass directive).
+ */
+ public function proxies(): Collection
+ {
+ $nginxPath = $this->nginxPath();
+ $domain = $this->config->get('domain');
+
+ $securedSites = $this->siteSecure->secured();
+
+ if (!$this->files->exists($nginxPath)) {
+ return collect();
+ }
+
+ return collect($this->files->scandir($nginxPath))
+ ->filter(function ($site) use ($domain) {
+ // keep sites that match our TLD
+ return str_ends_with($site, '.' . $domain);
+ })->mapWithKeys(function ($site) use ($domain) {
+ $host = $this->getProxyHostForSite($site) ?: '(other)';
+
+ return [$site => $host];
+ })->reject(function ($host) {
+ // If proxy host is null, it may be just a normal SSL stub, or something else;
+ // either way we exclude it from the list
+ return $host === '(other)';
+ })->map(function ($host, $site) use ($securedSites) {
+ $secured = $securedSites->contains($site);
+ $url = ($secured ? 'https' : 'http') . '://' . $site;
+
+ return [
+ 'url' => $url,
+ 'secured' => $secured ? '✓' : '✕',
+ 'path' => $host,
+ ];
+ });
+ }
+
+ /**
+ * Identify whether a site is for a proxy by reading the host name from its config file.
+ */
+ private function getProxyHostForSite(string $siteName): ?string
+ {
+ if ($this->files->exists($this->nginxPath($siteName)) === false) {
+ return null;
+ }
+
+ $siteConf = $this->files->get($this->nginxPath($siteName));
+ preg_match('~proxy_pass\s+(?https?://.*)\s*;~', $siteConf, $matches);
+
+ if (!isset($matches['host'])) {
+ return null;
+ }
+
+ return trim($matches['host']);
+ }
+}
diff --git a/cli/Valet/SiteSecure.php b/cli/Valet/SiteSecure.php
new file mode 100644
index 0000000..0a33e32
--- /dev/null
+++ b/cli/Valet/SiteSecure.php
@@ -0,0 +1,407 @@
+files = $filesystem;
+ $this->cli = $cli;
+ $this->config = $config;
+ }
+
+ /**
+ * Secure the given host with TLS.
+ */
+ public function secure(string $url, string $stub = null): void
+ {
+ if ($stub === null) {
+ $stub = $this->prepareConf($url, true);
+ }
+
+ $this->files->ensureDirExists($this->caPath(), user());
+
+ $this->files->ensureDirExists($this->certificatesPath(), user());
+
+ $caExpireInDate = (new \DateTime())->diff(new \DateTime("+20 years"));
+ $expiryInDays = (int)$caExpireInDate->format('%a'); // 20 years in days
+ $this->createCa($expiryInDays);
+
+ $certificateExpireInDate = (new \DateTime())->diff(new \DateTime("+1 year"));
+ $certificateExpireInDays = (int)$certificateExpireInDate->format('%a'); // 20 years in days
+ $this->createCertificate($url, $certificateExpireInDays);
+
+ $this->files->putAsUser(
+ $this->nginxPath($url),
+ $this->buildSecureNginxServer($url, $stub)
+ );
+ }
+
+ /**
+ * Unsecure the given URL so that it will use HTTP again.
+ */
+ public function unsecure(string $url, bool $preserveUnsecureConfig = false): void
+ {
+ $stub = null;
+ if ($this->files->exists($this->certificatesPath($url . '.crt'))) {
+ if ($preserveUnsecureConfig) {
+ $stub = $this->prepareConf($url);
+ }
+
+ $this->files->unlink($this->nginxPath($url));
+
+ $this->files->unlink($this->certificatesPath($url . '.conf'));
+ $this->files->unlink($this->certificatesPath($url . '.key'));
+ $this->files->unlink($this->certificatesPath($url . '.csr'));
+ $this->files->unlink($this->certificatesPath($url . '.crt'));
+ }
+
+ if ($stub) {
+ $stub = $this->buildUnsecureNginxServer($url, $stub);
+
+ $this->files->putAsUser(
+ $this->nginxPath($url),
+ $stub
+ );
+ }
+ }
+
+ /**
+ * Get all the URLs that are currently secured.
+ * @return Collection
+ */
+ public function secured(): Collection
+ {
+ return collect($this->files->scandir($this->certificatesPath()))
+ ->map(function ($file) {
+ return str_replace(['.key', '.csr', '.crt', '.conf'], '', $file);
+ })->unique()->values();
+ }
+
+ /**
+ * Regenerate all secured file configurations.
+ */
+ public function regenerateSecuredSitesConfig(): void
+ {
+ $this->secured()->each(function (string $url) {
+ $this->files->putAsUser(
+ $this->nginxPath($url),
+ $this->buildSecureNginxServer($url)
+ );
+ });
+ }
+
+ /**
+ * Re-secure all currently secured sites with a fresh domain.
+ */
+ public function reSecureForNewDomain(string $oldDomain, string $domain): void
+ {
+ if (!$this->files->exists($this->certificatesPath())) {
+ return;
+ }
+
+ $secured = $this->secured();
+
+ foreach ($secured as $oldUrl) {
+ $newUrl = str_replace('.' . $oldDomain, '.' . $domain, $oldUrl);
+ $hasConf = $this->files->exists($this->nginxPath($oldUrl));
+ $nginxConf = null;
+ if ($hasConf) {
+ $nginxConf = $this->files->get($this->nginxPath($oldUrl));
+ $nginxConf = str_replace($oldUrl, $newUrl, $nginxConf);
+ }
+
+ $this->unsecure($oldUrl);
+
+ $this->secure($newUrl, $nginxConf);
+ }
+ }
+
+ /**
+ * 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.
+ * @throws \Exception
+ */
+ private function createCa(int $caExpireInDays): void
+ {
+ $caPemPath = $this->caPath($this->caCertificatePem);
+ $caKeyPath = $this->caPath($this->caCertificateKey);
+
+ if ($this->files->exists($caKeyPath) && $this->files->exists($caPemPath)) {
+ $this->trustCa($caPemPath);
+ return;
+ }
+
+ if ($this->files->exists($caKeyPath)) {
+ $this->files->unlink($caKeyPath);
+ }
+ if ($this->files->exists($caPemPath)) {
+ $this->files->unlink($caPemPath);
+ }
+
+ $this->unTrustCa();
+
+ $subject = sprintf(
+ '/C=/ST=/O=%s/localityName=/commonName=%s/organizationalUnitName=Developers/emailAddress=%s/',
+ $this->caCertificateOrganization,
+ $this->caCertificateCommonName,
+ $this->certificateDummyEmail,
+ );
+ $this->cli->runAsUser(
+ sprintf(
+ 'openssl req -new -newkey rsa:2048 -days %s -nodes -x509 -subj "%s" -keyout "%s" -out "%s"',
+ $caExpireInDays,
+ $subject,
+ $caKeyPath,
+ $caPemPath
+ )
+ );
+ $this->trustCa($caPemPath);
+ }
+
+ /**
+ * Trust the given root certificate file in the macOS Keychain.
+ * @throws \Exception
+ */
+ private function unTrustCa(): void
+ {
+ $this->files->remove(\sprintf('%s%s.crt', $this->caCertificatePath, $this->caCertificatePem));
+ $this->cli->run('sudo update-ca-certificates');
+ }
+
+ /**
+ * Trust the given root certificate file in the macOS Keychain.
+ */
+ private function trustCa(string $caPemPath): void
+ {
+ $this->files->copy($caPemPath, sprintf('%s%s.crt', $this->caCertificatePath, $this->caCertificatePem));
+ $this->cli->run('sudo update-ca-certificates');
+
+ $this->cli->runAsUser(sprintf(
+ 'certutil -d sql:$HOME/.pki/nssdb -A -t TC -n "%s" -i "%s"',
+ $this->caCertificateOrganization,
+ $caPemPath
+ ));
+
+ $this->cli->runAsUser(sprintf(
+ 'certutil -d $HOME/.mozilla/firefox/*.default -A -t TC -n "%s" -i "%s"',
+ $this->caCertificateOrganization,
+ $caPemPath
+ ));
+
+ $this->cli->runAsUser(sprintf(
+ 'certutil -d $HOME/snap/firefox/common/.mozilla/firefox/*.default -A -t TC -n "%s" -i "%s"',
+ $this->caCertificateOrganization,
+ $caPemPath
+ ));
+ }
+
+ /**
+ * Create and trust a certificate for the given URL.
+ */
+ private function createCertificate(string $url, int $certificateExpireInDays = 368): void
+ {
+ $caPemPath = $this->caPath($this->caCertificatePem);
+ $caKeyPath = $this->caPath($this->caCertificateKey);
+ $caSrlPath = $this->caPath($this->caCertificateSrl);
+
+ $keyPath = $this->certificatesPath() . '/' . $url . '.key';
+ $csrPath = $this->certificatesPath() . '/' . $url . '.csr';
+ $crtPath = $this->certificatesPath() . '/' . $url . '.crt';
+ $confPath = $this->certificatesPath() . '/' . $url . '.conf';
+
+ $this->generateCertificateConf($confPath, $url);
+ $this->cli->runAsUser(sprintf('openssl genrsa -out %s 2048', $keyPath));
+
+ $subject = sprintf(
+ '/C=/ST=/O=/localityName=/commonName=%s/organizationalUnitName=/emailAddress=%s/',
+ $url,
+ $this->certificateDummyEmail,
+ );
+ $this->cli->runAsUser(sprintf(
+ 'openssl req -new -key %s -out %s -subj "%s" -config %s',
+ $keyPath,
+ $csrPath,
+ $subject,
+ $confPath
+ ));
+
+ $caSrlParam = '-CAserial "' . $caSrlPath . '"';
+ if (! $this->files->exists($caSrlPath)) {
+ $caSrlParam .= ' -CAcreateserial';
+ }
+
+ $this->cli->run(sprintf(
+ 'openssl x509 -req -sha256 -days %s -CA "%s" -CAkey "%s" %s -in %s -out %s -extensions v3_req -extfile %s',
+ $certificateExpireInDays,
+ $caPemPath,
+ $caKeyPath,
+ $caSrlParam,
+ $csrPath,
+ $crtPath,
+ $confPath
+ ));
+ }
+
+ /**
+ * Build the TLS secured Nginx server for the given URL.
+ */
+ public function buildSecureNginxServer(string $url, ?string $stub = null): string
+ {
+ $stub = ($stub ?: $this->files->get(VALET_ROOT_PATH . '/cli/stubs/secure.valet.conf'));
+ $path = $this->certificatesPath();
+
+ return strArrayReplace(
+ [
+ 'VALET_HOME_PATH' => VALET_HOME_PATH,
+ 'VALET_SERVER_PATH' => VALET_SERVER_PATH,
+ 'VALET_STATIC_PREFIX' => VALET_STATIC_PREFIX,
+ 'VALET_SITE' => $url,
+ 'VALET_CERT' => $path . '/' . $url . '.crt',
+ 'VALET_KEY' => $path . '/' . $url . '.key',
+ 'VALET_HTTP_PORT' => $this->config->get('port', 80),
+ 'VALET_HTTPS_PORT' => $this->config->get('https_port', 443),
+ 'VALET_REDIRECT_PORT' => $this->httpsSuffix(),
+ 'VALET_FPM_SOCKET_FILE' => PhpFpmFacade::fpmSocketFile(PhpFpmFacade::getCurrentVersion()),
+ ],
+ $stub
+ );
+ }
+
+ /**
+ * Build the TLS secured Nginx server for the given URL.
+ */
+ public function buildUnsecureNginxServer(string $url, string $stub): string
+ {
+ $this->files->ensureDirExists($this->nginxPath(), user());
+
+ return strArrayReplace(
+ [
+ 'VALET_HOME_PATH' => VALET_HOME_PATH,
+ 'VALET_SERVER_PATH' => VALET_SERVER_PATH,
+ 'VALET_STATIC_PREFIX' => VALET_STATIC_PREFIX,
+ 'VALET_SITE' => $url,
+ 'VALET_HTTP_PORT' => $this->config->get('port', 80),
+ 'VALET_HTTPS_PORT' => $this->config->get('https_port', 443),
+ ],
+ $stub
+ );
+ }
+
+ /**
+ * Prepare Nginx Conf based on existing config file.
+ */
+ private function prepareConf(string $url, bool $secure = false): ?string
+ {
+ if (!$this->files->exists($this->nginxPath($url))) {
+ return null;
+ }
+
+ $existingConf = $this->files->get($this->nginxPath($url));
+
+ preg_match('/# valet stub: (?secure)?\.?(?.*?).valet.conf/m', $existingConf, $stubDetail);
+
+ if (empty($stubDetail['stub'])) {
+ return null;
+ }
+
+ if ($stubDetail['stub'] === 'proxy') {
+ // Find proxy_pass from existingConf.
+ $proxyPass = $this->getProxyPass($url, $existingConf);
+ if (!$proxyPass) {
+ return null;
+ }
+ $stub = $secure ?
+ VALET_ROOT_PATH . '/cli/stubs/secure.proxy.valet.conf' :
+ VALET_ROOT_PATH . '/cli/stubs/proxy.valet.conf';
+ $stub = $this->files->get($stub);
+
+ return strArrayReplace([
+ 'VALET_PROXY_HOST' => $proxyPass,
+ ], $stub);
+ }
+
+ if ($stubDetail['stub'] === 'isolated') {
+ $phpVersion = $this->isolatedPhpVersion($existingConf);
+ // empty($stubDetail['tls']) || We can use this statement if needed.
+ $stub = $secure ?
+ VALET_ROOT_PATH . '/cli/stubs/secure.isolated.valet.conf' :
+ VALET_ROOT_PATH . '/cli/stubs/isolated.valet.conf';
+ $stub = $this->files->get($stub);
+ // Isolate specific variables
+ return strArrayReplace([
+ 'VALET_FPM_SOCKET_FILE' => PhpFpmFacade::fpmSocketFile($phpVersion),
+ 'VALET_ISOLATED_PHP_VERSION' => $phpVersion,
+ ], $stub);
+ }
+
+ return null;
+ }
+
+ /**
+ * Extract Proxy pass of exising nginx config.
+ */
+ private function getProxyPass(string $url, string $siteConf = null): ?string
+ {
+ if ($siteConf === null && !$this->files->exists($this->nginxPath($url))) {
+ return null;
+ }
+
+ $siteConf = $siteConf ?: $this->files->get($this->nginxPath($url));
+ preg_match('/proxy_pass (?.*?);/m', $siteConf, $matches);
+
+ return $matches['host'] ?? null;
+ }
+
+ /**
+ * Build the SSL config for the given URL.
+ */
+ private function generateCertificateConf(string $path, string $url): void
+ {
+ $config = str_replace('VALET_DOMAIN', $url, $this->files->get(VALET_ROOT_PATH . '/cli/stubs/openssl.conf'));
+ $this->files->putAsUser($path, $config);
+ }
+
+ /**
+ * Return https port suffix.
+ */
+ private function httpsSuffix(): string
+ {
+ $port = $this->config->get('https_port', 443);
+
+ return ($port == 443) ? '' : ':' . $port;
+ }
+
+ /**
+ * Extract PHP version of exising nginx config.
+ */
+ private function isolatedPhpVersion(string $siteConf): string
+ {
+ if (str_contains($siteConf, '# ' . ISOLATED_PHP_VERSION)) {
+ preg_match('/^# ISOLATED_PHP_VERSION=(.*?)\n/m', $siteConf, $version);
+ return $version[1];
+ }
+
+ return '';
+ }
+}
diff --git a/cli/Valet/Traits/Paths.php b/cli/Valet/Traits/Paths.php
new file mode 100644
index 0000000..03906ec
--- /dev/null
+++ b/cli/Valet/Traits/Paths.php
@@ -0,0 +1,38 @@
+cli->run('ln -snf '.realpath(__DIR__.'/../../valet').' '.$this->valetBin);
+ $this->cli->run('ln -snf ' . realpath(VALET_ROOT_PATH . '/valet') . ' ' . $this->valetBin);
+ }
+
+ /**
+ * Symlink the Valet Bash script into the user's local bin.
+ */
+ public function symlinkPhpToUsersBin(): void
+ {
+ $fallbackBin = '/usr/bin/php';
+ $phpBin = $_SERVER['_'] ?? $fallbackBin;
+ $phpBin = $this->files->realpath($phpBin);
+ if ($phpBin !== VALET_ROOT_PATH . 'php') {
+ ConfigurationFacade::set('fallback_binary', $phpBin);
+ } else {
+ ConfigurationFacade::set('fallback_binary', $fallbackBin);
+ }
+
+ $this->cli->run('ln -snf ' . realpath(VALET_ROOT_PATH . '/php') . ' ' . $this->phpBin);
}
/**
@@ -68,24 +70,25 @@ public function symlinkToUsersBin(): void
public function uninstall(): void
{
$this->files->unlink($this->valetBin);
- $this->files->unlink($this->sudoers);
+ $this->files->unlink($this->phpBin);
}
/**
* Get the paths to all the Valet extensions.
+ * @return array
*/
public function extensions(): array
{
- if (!$this->files->isDir(VALET_HOME_PATH.'/Extensions')) {
+ if (!$this->files->isDir(VALET_HOME_PATH . '/Extensions')) {
return [];
}
- return collect($this->files->scandir(VALET_HOME_PATH.'/Extensions'))
+ return collect($this->files->scandir(VALET_HOME_PATH . '/Extensions'))
->reject(function ($file) {
- return is_dir($file);
+ return $this->files->isDir($file);
})
->map(function ($file) {
- return VALET_HOME_PATH.'/Extensions/'.$file;
+ return VALET_HOME_PATH . '/Extensions/' . $file;
})
->values()->all();
}
@@ -96,7 +99,8 @@ public function extensions(): array
*/
public function onLatestVersion(string $currentVersion): bool
{
- $response = Request::get($this->github)->send();
+ $response = RequestFacade::get($this->github)->send();
+
$currentVersion = str_replace('v', '', $currentVersion);
$latestVersion = isset($response->body->tag_name) ? trim($response->body->tag_name) : 'v1.0.0';
$latestVersion = str_replace('v', '', $latestVersion);
@@ -111,7 +115,7 @@ public function onLatestVersion(string $currentVersion): bool
*/
public function getLatestVersion()
{
- $response = Request::get($this->github)->send();
+ $response = RequestFacade::get($this->github)->send();
return isset($response->body->tag_name) ? trim($response->body->tag_name) : false;
}
@@ -125,6 +129,93 @@ public function environmentSetup(): void
$this->packageManagerSetup();
}
+ /**
+ * Migrate ~/.valet directory to ~/.config/valet directory
+ */
+ public function migrateConfig(): void
+ {
+ $newHomePath = VALET_HOME_PATH;
+ $oldHomePath = OLD_VALET_HOME_PATH;
+
+ // Check if new config home already exists, then skip the process
+ if ($this->files->isDir($newHomePath)) {
+ return;
+ }
+
+ // Fetch FPM running process
+ $fpmVersions = $this->getRunningFpmVersions($oldHomePath);
+
+ // Stop running fpm services
+ if (count($fpmVersions)) {
+ foreach ($fpmVersions as $fpmVersion) {
+ PhpFpmFacade::stop($fpmVersion);
+ }
+ }
+
+ // Copy directory
+ $this->files->copyDirectory($oldHomePath, $newHomePath);
+
+ // Replace $oldHomePath to $newHomePath in Certificates, Valet.conf file
+ $this->updateNginxConfFiles();
+
+ // Update phpfpm's socket file path in config
+ PhpFpmFacade::updateHomePath($oldHomePath, $newHomePath);
+
+ // Start fpm services again
+ if (count($fpmVersions)) {
+ foreach ($fpmVersions as $fpmVersion) {
+ PhpFpmFacade::restart($fpmVersion);
+ }
+ } else {
+ PhpFpmFacade::restart();
+ }
+
+ NginxFacade::restart();
+
+ Writer::info('Valet home directory is migrated successfully! Please re-run your command');
+ Writer::info(\sprintf('New home directory: %s', $newHomePath));
+ Writer::info(\sprintf('Please remove %s directory manually', $oldHomePath));
+ exit;
+ }
+
+ private function updateNginxConfFiles(): void
+ {
+ $newHomePath = VALET_HOME_PATH;
+ $oldHomePath = OLD_VALET_HOME_PATH;
+ $nginxPath = $newHomePath . '/Nginx';
+
+ $siteConfigs = $this->files->scandir($nginxPath);
+ foreach ($siteConfigs as $siteConfig) {
+ $filePath = \sprintf('%s/%s', $nginxPath, $siteConfig);
+ $content = $this->files->get($filePath);
+ $content = str_replace($oldHomePath, $newHomePath, $content);
+ $this->files->put($filePath, $content);
+ }
+
+ $sitesAvailableConf = $this->files->get(Nginx::SITES_AVAILABLE_CONF);
+ $sitesAvailableConf = str_replace($oldHomePath, $newHomePath, $sitesAvailableConf);
+ $this->files->put(Nginx::SITES_AVAILABLE_CONF, $sitesAvailableConf);
+
+ $nginxConfig = $this->files->get(Nginx::NGINX_CONF);
+ $nginxConfig = str_replace($oldHomePath, $newHomePath, $nginxConfig);
+ $this->files->put(Nginx::SITES_AVAILABLE_CONF, $nginxConfig);
+ }
+
+ private function getRunningFpmVersions(string $homePath): array
+ {
+ $runningVersions = [];
+
+ $files = $this->files->scandir($homePath);
+ foreach ($files as $file) {
+ preg_match('/valet(\d)(\d)\.sock/', $file, $matches);
+ if (count($matches) >= 2) {
+ $runningVersions[] = \sprintf('%d.%d', $matches[1], $matches[2]);
+ }
+ }
+
+ return $runningVersions;
+ }
+
/**
* Configure package manager.
*/
diff --git a/cli/Valet/ValetRedis.php b/cli/Valet/ValetRedis.php
index 3c42d54..42f6104 100644
--- a/cli/Valet/ValetRedis.php
+++ b/cli/Valet/ValetRedis.php
@@ -26,10 +26,6 @@ class ValetRedis
/**
* Create a new PHP FPM class instance.
*
- * @param PackageManager $pm
- * @param ServiceManager $sm
- * @param CommandLine $cli
- *
* @return void
*/
public function __construct(PackageManager $pm, ServiceManager $sm, CommandLine $cli)
@@ -44,8 +40,9 @@ public function __construct(PackageManager $pm, ServiceManager $sm, CommandLine
*/
public function install(): void
{
- $this->pm->ensureInstalled($this->pm->redisPackageName);
- $this->sm->enable($this->pm->redisPackageName);
+ $packageName = $this->pm->packageName('redis');
+ $this->pm->ensureInstalled($packageName);
+ $this->sm->enable($packageName);
}
/**
@@ -53,7 +50,7 @@ public function install(): void
*/
public function installed(): bool
{
- return $this->pm->installed($this->pm->redisPackageName);
+ return $this->pm->installed($this->pm->packageName('redis'));
}
/**
@@ -61,7 +58,7 @@ public function installed(): bool
*/
public function restart(): void
{
- $this->sm->restart($this->pm->redisPackageName);
+ $this->sm->restart($this->pm->packageName('redis'));
}
/**
@@ -69,7 +66,7 @@ public function restart(): void
*/
public function stop(): void
{
- $this->sm->stop($this->pm->redisPackageName);
+ $this->sm->stop($this->pm->packageName('redis'));
}
/**
diff --git a/cli/app.php b/cli/app.php
index dbd8f3d..717572b 100644
--- a/cli/app.php
+++ b/cli/app.php
@@ -1,15 +1,13 @@
command('install [--ignore-selinux] [--mariadb]', function ($ignoreSELinux, $mariaDB) {
- passthru(dirname(__FILE__).'/scripts/update.sh'); // Clean up cruft
+$app->command('install [--ignore-selinux]', function ($ignoreSELinux) {
+ Writer::info('Installing valet services');
+
+ passthru(dirname(__FILE__) . '/scripts/update.sh'); // Clean up cruft
Requirements::setIgnoreSELinux($ignoreSELinux)->check();
Configuration::install();
Nginx::install();
PhpFpm::install();
- DnsMasq::install(Configuration::read()['domain']);
- Valet::symlinkToUsersBin();
+ DnsMasq::install(Configuration::get('domain'));
Mailpit::install();
ValetRedis::install();
Nginx::restart();
- Mysql::install($mariaDB);
+ Mysql::install();
+ Ngrok::install();
+ Valet::symlinkToUsersBin();
- output(PHP_EOL.'Valet installed successfully!');
+ Writer::info('Valet installed successfully!');
+
+ $canLinkValetPhp = Writer::confirm('Do you want to link valet\'s php binary?', true);
+ if ($canLinkValetPhp) {
+ Valet::symlinkPhpToUsersBin();
+ }
+
+ if ($canLinkValetPhp) {
+ Writer::info('Valet executable php helper is linked to /usr/local/bin/php.');
+ }
})->descriptions('Install the Valet services', [
'--ignore-selinux' => 'Skip SELinux checks',
]);
-
/**
* Most commands are available only if valet is installed.
*/
@@ -92,7 +102,7 @@
Mailpit::restart();
Mysql::restart();
ValetRedis::restart();
- info('Valet services have been started.');
+ Writer::info('Valet services have been started.');
return;
}
@@ -126,7 +136,7 @@
}
}
- info('Specified Valet services have been started.');
+ Writer::info('Specified Valet services have been started.');
})->descriptions('Start the Valet services');
/**
@@ -140,7 +150,7 @@
Mailpit::restart();
Mysql::restart();
ValetRedis::restart();
- info('Valet services have been restarted.');
+ Writer::info('Valet services have been restarted.');
return;
}
@@ -175,7 +185,7 @@
}
}
- info('Specified Valet services have been restarted.');
+ Writer::info('Specified Valet services have been restarted.');
})->descriptions('Restart the Valet services');
/**
@@ -188,7 +198,7 @@
Mailpit::stop();
Mysql::stop();
ValetRedis::stop();
- info('Valet services have been stopped.');
+ Writer::info('Valet services have been stopped.');
return;
}
@@ -219,7 +229,7 @@
}
}
- info('Specified Valet services have been stopped.');
+ Writer::info('Specified Valet services have been stopped.');
})->descriptions('Stop the Valet services');
/**
@@ -233,7 +243,7 @@
Configuration::uninstall();
Valet::uninstall();
- info('Valet has been uninstalled.');
+ Writer::info('Valet has been uninstalled.');
})->descriptions('Uninstall the Valet services');
/**
@@ -247,54 +257,54 @@
/**
* Determine if this is the latest release of Valet.
*/
- $app->command('is-latest', function () {
- if (Valet::onLatestVersion(VALET_VERSION)) {
- output('YES');
+ $app->command('is-latest', function () use ($version) {
+ if (Valet::onLatestVersion($version)) {
+ Writer::info('YES');
} else {
- output('NO');
+ Writer::info('NO');
}
})->descriptions('Determine if this is the latest version of Valet');
/**
* Determine if this is the latest release of Valet.
*/
- $app->command('update', function () {
- $script = dirname(__FILE__).'/scripts/update.sh';
+ $app->command('update', function () use ($version) {
+ $script = dirname(__FILE__) . '/scripts/update.sh';
- if (Valet::onLatestVersion(VALET_VERSION)) {
- info('You have the latest version of Valet Linux');
+ if (Valet::onLatestVersion($version)) {
+ Writer::info('You have the latest version of Valet Linux+');
passthru($script);
} else {
- warning('There is a new release of Valet Linux');
- warning('Updating now...');
+ Writer::warn('There is a new release of Valet Linux+');
+ Writer::warn('Updating now...');
$latestVersion = Valet::getLatestVersion();
if ($latestVersion) {
- passthru($script." update $latestVersion");
+ passthru($script . " update $latestVersion");
} else {
- passthru($script.' update');
+ passthru($script . ' update');
}
}
- })->descriptions('Update Valet Linux and clean up cruft');
+ })->descriptions('Update Valet Linux+ and clean up cruft');
/**
* Get or set the domain currently being used by Valet.
*/
$app->command('domain [domain]', function ($domain = null) {
if ($domain === null) {
- info(Configuration::read()['domain']);
+ Writer::info(sprintf('Your current Valet domain is [%s].', Configuration::get('domain')));
return;
}
DnsMasq::updateDomain($domain = trim($domain, '.'));
- $oldDomain = Configuration::read()['domain'];
+ $oldDomain = Configuration::get('domain');
- Configuration::updateKey('domain', $domain);
- Site::resecureForNewDomain($oldDomain, $domain);
+ Configuration::set('domain', $domain);
+ SiteSecure::reSecureForNewDomain($oldDomain, $domain);
PhpFpm::restart();
Nginx::restart();
- info('Your Valet domain has been updated to ['.$domain.'].');
+ Writer::info('Your Valet domain has been updated to [' . $domain . '].');
})->descriptions('Get or set the domain used for Valet sites');
/**
@@ -302,8 +312,8 @@
*/
$app->command('port [port] [--https]', function ($port, $https) {
if ($port === null) {
- info('Current Nginx port (HTTP): '.Configuration::get('port', 80));
- info('Current Nginx port (HTTPS): '.Configuration::get('https_port', 443));
+ Writer::info('Current Nginx port (HTTP): ' . Configuration::get('port', 80));
+ Writer::info('Current Nginx port (HTTPS): ' . Configuration::get('https_port', 443));
return;
}
@@ -311,19 +321,19 @@
$port = trim($port);
if ($https) {
- Configuration::updateKey('https_port', $port);
+ Configuration::set('https_port', $port);
} else {
Nginx::updatePort($port);
- Configuration::updateKey('port', $port);
+ Configuration::set('port', $port);
}
- Site::regenerateSecuredSitesConfig();
+ SiteSecure::regenerateSecuredSitesConfig();
Nginx::restart();
PhpFpm::restart();
$protocol = $https ? 'HTTPS' : 'HTTP';
- info("Your Nginx $protocol port has been updated to [$port].");
+ Writer::info("Your Nginx $protocol port has been updated to [$port].");
})->descriptions('Get or set the port number used for Valet sites');
/**
@@ -333,9 +343,9 @@
$driver = ValetDriver::assign(getcwd(), basename(getcwd()), '/');
if ($driver) {
- info('This site is served by ['.get_class($driver).'].');
+ Writer::info('This site is served by [' . get_class($driver) . '].');
} else {
- warning('Valet could not determine which driver to use for this site.');
+ Writer::warn('Valet could not determine which driver to use for this site.');
}
})->descriptions('Determine which Valet driver serves the current working directory');
@@ -343,21 +353,26 @@
* Add the current working directory to paths configuration.
*/
$app->command('park [path]', function ($path = null) {
- Configuration::addPath($path ?: getcwd());
+ $path = $path ?: getcwd();
+ Configuration::addPath($path);
- info(($path === null ? 'This' : "The [$path]")." directory has been added to Valet's paths.");
+ Writer::info("The [$path] directory has been added to Valet's paths.");
})->descriptions('Register the current working (or specified) directory with Valet');
/**
* Display all the registered paths.
*/
$app->command('paths', function () {
- $paths = Configuration::read()['paths'];
+ $paths = Configuration::get('paths');
if (count($paths) > 0) {
- info(json_encode($paths, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
+ $paths = array_map(function ($path) {
+ return [$path];
+ }, $paths);
+
+ Writer::table(['Path'], $paths);
} else {
- warning('No paths have been registered.');
+ Writer::warn('No paths have been registered.');
}
})->descriptions('Get all of the paths registered with Valet');
@@ -365,17 +380,43 @@
* Remove the current working directory from paths configuration.
*/
$app->command('forget [path]', function ($path = null) {
- Configuration::removePath($path ?: getcwd());
+ $path = $path ?: getcwd();
+ Configuration::removePath($path);
- info(($path === null ? 'This' : "The [$path]")." directory has been removed from Valet's paths.");
+ Writer::info("The [$path] directory has been removed from Valet's paths.");
})->descriptions('Remove the current working (or specified) directory from Valet\'s list of paths');
/**
* Create Nginx proxy config for the specified domain.
*/
- $app->command('proxy domain host [--secure]', function ($domain, $host, $secure) {
- Site::proxyCreate($domain, $host, $secure);
+ $app->command('proxy [domain] [host] [--secure]', function ($domain, $host, $secure) {
+ if ($domain === null) {
+ Writer::error('Please provide domain');
+ return;
+ }
+
+ if ($host === null) {
+ Writer::error('Please provide host');
+ return;
+ }
+
+ if (!preg_match('~^https?://.*$~', $host)) {
+ Writer::error(sprintf('"%s" is not a valid URL', $host));
+ return;
+ }
+
+ $tld = Configuration::get('domain');
+
+ if (!str_ends_with($domain, '.' . $tld)) {
+ $domain .= '.' . $tld;
+ }
+
+ SiteProxy::proxyCreate($domain, $host, $secure);
Nginx::restart();
+
+ $protocol = $secure ? 'https' : 'http';
+
+ Writer::info('Valet will now proxy [' . $protocol . '://' . $domain . '] traffic to [' . $host . '].');
})->descriptions('Create an Nginx proxy site for the specified host. Useful for docker, node etc.', [
'--secure' => 'Create a proxy with a trusted TLS certificate',
]);
@@ -383,136 +424,139 @@
/**
* Delete Nginx proxy config.
*/
- $app->command('unproxy domain', function ($domain) {
- Site::proxyDelete($domain);
+ $app->command('unproxy [domain]', function ($domain) {
+ if ($domain === null) {
+ Writer::error('Please provide domain');
+ return;
+ }
+
+ $tld = Configuration::get('domain');
+ if (!str_ends_with($domain, '.' . $tld)) {
+ $domain .= '.' . $tld;
+ }
+
+ SiteSecure::unsecure($domain);
Nginx::restart();
+
+ Writer::info('Valet will no longer proxy [' . $domain . '].');
})->descriptions('Delete an Nginx proxy config.');
/**
* Display all the sites that are proxies.
*/
$app->command('proxies', function () {
- $proxies = Site::proxies();
+ $proxies = SiteProxy::proxies();
- table(['URL', 'SSL', 'Host'], $proxies->all());
+ Writer::table(['URL', 'SSL', 'Host'], $proxies->all());
})->descriptions('Display all of the proxy sites');
/**
* Register a symbolic link with Valet.
*/
$app->command('link [name]', function ($name) {
- $linkPath = Site::link(getcwd(), $name = $name ?: basename(getcwd()));
+ $name = $name ?: basename(getcwd());
+ $linkPath = SiteLink::link(getcwd(), $name);
- info('A ['.$name.'] symbolic link has been created in ['.$linkPath.'].');
+ Writer::info('A [' . $name . '] symbolic link has been created in [' . $linkPath . '].');
})->descriptions('Link the current working directory to Valet');
/**
* Unlink a link from the Valet links directory.
*/
$app->command('unlink [name]', function ($name) {
- Site::unlink($name = $name ?: basename(getcwd()));
+ $name = $name ?: basename(getcwd());
+ SiteLink::unlink($name);
- info('The ['.$name.'] symbolic link has been removed.');
+ Writer::info('The [' . $name . '] symbolic link has been removed.');
})->descriptions('Remove the specified Valet link');
/**
* Display all the registered symbolic links.
*/
$app->command('links', function () {
- $links = Site::links();
+ $links = SiteLink::links();
- table(['Site', 'SSL', 'URL', 'Path', 'PHP Version'], $links->all());
+ Writer::table(['URL', 'SSL', 'Path'], $links->all());
})->descriptions('Display all of the registered Valet links');
/**
* Secure the given domain with a trusted TLS certificate.
*/
$app->command('secure [domain]', function ($domain = null) {
- $url = ($domain ?: Site::host(getcwd())).'.'.Configuration::read()['domain'];
+ $url = ($domain ?: basename(getcwd()));
+ $url = Configuration::parseDomain($url);
- Site::secure($url);
+ SiteSecure::secure($url);
Nginx::restart();
- info('The ['.$url.'] site has been secured with a fresh TLS certificate.');
+ Writer::info('The [' . $url . '] site has been secured with a fresh TLS certificate.');
})->descriptions('Secure the given domain with a trusted TLS certificate');
/**
* Stop serving the given domain over HTTPS and remove the trusted TLS certificate.
*/
$app->command('unsecure [domain]', function ($domain = null) {
- $url = ($domain ?: Site::host(getcwd())).'.'.Configuration::read()['domain'];
+ $url = ($domain ?: basename(getcwd()));
+ $url = Configuration::parseDomain($url);
- Site::unsecure($url, true);
+ SiteSecure::unsecure($url, true);
Nginx::restart();
- info('The ['.$url.'] site will now serve traffic over HTTP.');
+ Writer::info('The [' . $url . '] site will now serve traffic over HTTP.');
})->descriptions('Stop serving the given domain over HTTPS and remove the trusted TLS certificate');
/**
* Determine if the site is secured or not.
*/
$app->command('secured [site]', function ($site) {
- if (Site::secured()->contains($site)) {
- info("$site is secured.");
- return 1;
- }
+ $site = $site ?: basename(getcwd());
+ $site = Configuration::parseDomain($site);
- info("$site is not secured.");
- return 0;
- })->descriptions('Determine if the site is secured or not');
-
- /**
- * Register a subdomain link.
- */
- $app->command('subdomain:create [name] [--secure]', function ($name, $secure) {
- $name = $name ?: 'www';
- Site::link(getcwd(), $name.'.'.basename(getcwd()));
-
- if ($secure) {
- $this->runCommand('secure '.$name);
+ if (SiteSecure::secured()->contains($site)) {
+ Writer::info("$site is secured.");
+ return;
}
- $domain = Configuration::read()['domain'];
-
- info('Subdomain '.$name.'.'.basename(getcwd()).'.'.$domain.' created');
- })->descriptions('Create a subdomains');
- /**
- * Unregister a subdomain link.
- */
- $app->command('subdomain:remove [name]', function ($name) {
- $name = $name ?: 'www';
- Site::unlink($name.'.'.basename(getcwd()));
- $domain = Configuration::read()['domain'];
- info('Subdomain '.$name.'.'.basename(getcwd()).'.'.$domain.' removed');
- })->descriptions('Remove a subdomains');
-
- /**
- * List subdomains.
- */
- $app->command('subdomain:list', function () {
- $links = Site::links();
- table(['Site', 'SSL', 'URL', 'Path'], $links->all());
- })->descriptions('List all subdomains');
+ Writer::info("$site is not secured.");
+ })->descriptions('Determine if the site is secured or not');
/**
* Change the PHP version to the desired one.
*/
- $app->command('use [preferredVersion] [--update-cli] [--ignore-ext] [--ignore-update]', function (
+ $app->command('use [preferredVersion] [--update-cli] [--ignore-ext]', function (
$preferredVersion = null,
$updateCli = null,
- $ignoreExt = null,
- $ignoreUpdate = null
+ $ignoreExt = null
) {
- info('Changing php version...');
- PhpFpm::switchVersion($preferredVersion, $updateCli, $ignoreExt, $ignoreUpdate);
- info('php version successfully changed!');
+ $preferredVersion = PhpFpm::normalizePhpVersion($preferredVersion);
+ $isValid = PhpFpm::validateVersion($preferredVersion);
+ if (!$isValid) {
+ Writer::error(
+ sprintf(
+ "Invalid version [%s] used. Supported versions are: %s",
+ $preferredVersion,
+ implode(', ', \Valet\PhpFpm::SUPPORTED_PHP_VERSIONS)
+ )
+ );
+ Writer::info(
+ sprintf(
+ 'You can still use any version from [%s] list using `valet isolate` command',
+ implode(', ', \Valet\PhpFpm::ISOLATION_SUPPORTED_PHP_VERSIONS)
+ )
+ );
+ return;
+ }
+
+ PhpFpm::switchVersion($preferredVersion, $updateCli, $ignoreExt);
+ Writer::info(sprintf('PHP version successfully changed to [%s]', $preferredVersion));
})->descriptions(
- 'Set the PHP version to use, enter "default" or leave empty to use version: '
- .PhpFpm::getCurrentVersion(),
+ sprintf(
+ 'Set the PHP version to use, enter "default" or leave empty to use version: %s',
+ PHP_VERSION
+ ),
[
'--update-cli' => 'Updates CLI version as well',
'--ignore-ext' => 'Installs extension with selected php version',
- '--ignore-update' => 'Ignores self package update. Works with --update-cli flag.',
]
);
@@ -520,63 +564,73 @@
* List MySQL Database.
*/
$app->command('db:list', function () {
- Mysql::listDatabases();
+ $databases = Mysql::getDatabases();
+
+ Writer::table(['Database'], $databases);
})->descriptions('List all available database in MySQL/MariaDB');
/**
* Create new database in MySQL.
*/
$app->command('db:create [databaseName]', function ($databaseName) {
- Mysql::createDatabase($databaseName);
+ $databaseName = $databaseName ?: basename((string)getcwd());
+
+ $isCreated = Mysql::createDatabase($databaseName);
+ if ($isCreated) {
+ Writer::info(sprintf('Database [%s] created successfully', $databaseName));
+ }
})->descriptions('Create new database in MySQL/MariaDB');
/**
* Drop database in MySQL.
*/
- $app->command('db:drop [databaseName] [-y|--yes]', function (Input $input, $output, $databaseName) {
- $helper = $this->getHelperSet()->get('question');
- $defaults = $input->getOptions();
- if (!$defaults['yes']) {
- $question = new ConfirmationQuestion('Are you sure you want to delete the database? [y/N] ', false);
- if (!$helper->ask($input, $output, $question)) {
- warning('Aborted');
+ $app->command('db:drop [databaseName] [-y|--yes]', function ($databaseName, $yes) {
+ $databaseName = $databaseName ?: basename((string)getcwd());
+
+ if (!$yes) {
+ $confirm = Writer::confirm(sprintf('Are you sure you want to delete [%s] database?', $databaseName));
+ if (!$confirm) {
+ Writer::warn('Aborted');
return;
}
}
- Mysql::dropDatabase($databaseName);
+ $isDropped = Mysql::dropDatabase($databaseName);
+ if ($isDropped) {
+ Writer::info(sprintf('Database [%s] dropped successfully', $databaseName));
+ }
})->descriptions('Drop given database from MySQL/MariaDB');
/**
* Reset database in MySQL.
*/
- $app->command('db:reset [databaseName] [-y|--yes]', function (Input $input, $output, $databaseName) {
- $helper = $this->getHelperSet()->get('question');
- $defaults = $input->getOptions();
- if (!$defaults['yes']) {
- $question = new ConfirmationQuestion('Are you sure you want to reset the database? [y/N] ', false);
- if (!$helper->ask($input, $output, $question)) {
- warning('Aborted');
+ $app->command('db:reset [databaseName] [-y|--yes]', function ($databaseName, $yes) {
+ $databaseName = $databaseName ?: basename((string)getcwd());
+
+ if (!$yes) {
+ $confirm = Writer::confirm(sprintf('Are you sure you want to reset [%s] database?', $databaseName));
+ if (!$confirm) {
+ Writer::warn('Aborted');
return;
}
}
$dropDB = Mysql::dropDatabase($databaseName);
if (!$dropDB) {
- warning('Error resetting database');
+ Writer::warn('Error resetting database');
return;
}
- $databaseName = Mysql::createDatabase($databaseName);
+ $isCreated = Mysql::createDatabase($databaseName);
- if (!$databaseName) {
- warning('Error resetting database');
+ if (!$isCreated) {
+ Writer::warn('Error resetting database');
return;
}
- info("Database [$databaseName] reset successfully");
+ Writer::info(sprintf('Database [%s] reset successfully', $databaseName));
})->descriptions('Clear all tables for given database in MySQL/MariaDB');
/**
@@ -584,44 +638,37 @@
*
* @throws Exception
*/
- $app->command('db:import [databaseName] [dumpFile]', function (Input $input, $output, $databaseName, $dumpFile) {
- $helper = $this->getHelperSet()->get('question');
- info('Importing database...');
+ $app->command('db:import [databaseName] [dumpFile]', function ($databaseName, $dumpFile) {
if (!$databaseName) {
- throw new DatabaseException('Please provide database name');
+ Writer::error('Please provide database name');
+ return;
}
if (!$dumpFile) {
- throw new DatabaseException('Please provide a dump file');
- }
- if (!file_exists($dumpFile)) {
- throw new DatabaseException("Unable to locate [$dumpFile]");
+ Writer::error('Please provide a dump file path');
+ return;
}
- $isExistsDatabase = false;
- // check if database already exists.
- if (Mysql::isDatabaseExists($databaseName)) {
- $question = new ConfirmationQuestion(
- 'Database already exists are you sure you want to continue? [y/N] ',
- false
- );
- if (!$helper->ask($input, $output, $question)) {
- warning('Aborted');
- return;
- }
- $isExistsDatabase = true;
+ if (!Filesystem::exists($dumpFile)) {
+ Writer::error(sprintf('Unable to locate [%s]', $dumpFile));
+ return;
}
+ Writer::info('Importing database...');
- Mysql::importDatabase($dumpFile, $databaseName, $isExistsDatabase);
+ Mysql::importDatabase($dumpFile, $databaseName);
+
+ Writer::info(sprintf('Database [%s] imported successfully', $databaseName));
})->descriptions('Import dump file for selected database in MySQL/MariaDB');
/**
* Export database in MySQL.
*/
- $app->command('db:export [databaseName] [--sql]', function (Input $input, $databaseName) {
- info('Exporting database...');
- $defaults = $input->getOptions();
- $data = Mysql::exportDatabase($databaseName, $defaults['sql']);
- info("Database [{$data['database']}] exported into file {$data['filename']}");
+ $app->command('db:export [databaseName] [--sql]', function ($databaseName, $sql) {
+ Writer::info('Exporting database...');
+ $databaseName = $databaseName ?: basename((string)getcwd());
+
+ $data = Mysql::exportDatabase($databaseName, $sql);
+
+ Writer::info(sprintf("Database [%s] exported into file %s", $data['database'], $data['filename']));
})->descriptions('Export selected MySQL/MariaDB database');
/**
@@ -668,17 +715,26 @@
*/
$app->command('isolate [phpVersion] [--site=] [--secure]', function ($phpVersion, $site, $secure) {
if (!$site) {
- $site = basename(getcwd());
+ $site = basename((string)getcwd());
}
- if (is_null($phpVersion) && $phpVersion = Site::phpRcVersion($site)) {
- info("Found '$site/.valetphprc' specifying version: $phpVersion");
+ if ($phpVersion === null && $phpVersion = Site::phpRcVersion($site)) {
+ Writer::info("Found '$site/.valetphprc' specifying version: $phpVersion");
}
- PhpFpm::isolateDirectory($site, $phpVersion, $secure);
+ if ($phpVersion === null) {
+ Writer::warn('Please select version to isolate');
+ return;
+ }
+
+ $isSuccess = SiteIsolate::isolateDirectory($site, $phpVersion, $secure);
+
+ if ($isSuccess) {
+ Writer::info(sprintf('The site [%s] is now using %s.', $site, $phpVersion));
+ }
})->descriptions('Change the version of PHP used by Valet to serve the current working directory', [
'phpVersion' => 'The PHP version you want to use; e.g php@8.1',
- '--site' => 'Specify the site to isolate (e.g. if the site isn\'t linked as its directory name)',
+ '--site' => 'Specify the site to isolate (e.g. if the site isn\'t linked as its directory name)',
'--secure' => 'Create a isolated site with a trusted TLS certificate',
]);
@@ -687,10 +743,12 @@
*/
$app->command('unisolate [--site=]', function ($site = null) {
if (!$site) {
- $site = basename(getcwd());
+ $site = basename((string)getcwd());
}
- PhpFpm::unIsolateDirectory($site);
+ SiteIsolate::unIsolateDirectory($site);
+
+ Writer::info(sprintf('The site [%s] is now using the default PHP version.', $site));
})->descriptions('Stop customizing the version of PHP used by Valet to serve the current working directory', [
'--site' => 'Specify the site to un-isolate (e.g. if the site isn\'t linked as its directory name)',
]);
@@ -699,24 +757,24 @@
* List isolated sites.
*/
$app->command('isolated', function () {
- $sites = PhpFpm::isolatedDirectories();
+ $sites = SiteIsolate::isolatedDirectories();
- table(['Path', 'PHP Version'], $sites->all());
+ Writer::table(['URL', 'SSL', 'PHP Version'], $sites->all());
})->descriptions('List all sites using isolated versions of PHP.');
/**
* Get the PHP executable path for a site.
*/
$app->command('which-php [site]', function ($site) {
- $phpVersion = Site::customPhpVersion(
- Site::host($site ?: getcwd()).'.'.Configuration::read()['domain']
- );
+ $site = basename($site ?: (string)getcwd());
+ $domain = Configuration::parseDomain($site);
+ $phpVersion = SiteIsolate::isolatedPhpVersion($domain);
if (!$phpVersion) {
$phpVersion = Site::phpRcVersion($site ?: basename(getcwd()));
}
- info(PhpFpm::getPhpExecutablePath($phpVersion));
+ echo PhpFpm::getPhpExecutablePath($phpVersion);
})->descriptions('Get the PHP executable path for a given site', [
'site' => 'The site to get the PHP executable path for',
]);
@@ -725,7 +783,7 @@
* Proxy commands through to an isolated site's version of PHP.
*/
$app->command('php [--site=] [command]', function () {
- warning(
+ Writer::warn(
'It looks like you are running `cli/valet.php` directly;
please use the `valet` script in the project root instead.'
);
@@ -738,7 +796,7 @@
* Proxy commands through to an isolated site's version of Composer.
*/
$app->command('composer [--site=] [command]', function () {
- warning('It looks like you are running `cli/valet.php` directly;
+ Writer::warn('It looks like you are running `cli/valet.php` directly;
please use the `valet` script in the project root instead.');
})->descriptions("Proxy Composer commands with isolated site's PHP executable", [
'command' => "Composer command to run with isolated site's PHP executable",
@@ -749,16 +807,20 @@
* Open the current directory in the browser.
*/
$app->command('open [domain]', function ($domain = null) {
- $url = 'http://'.($domain ?: Site::host(getcwd())).'.'.Configuration::read()['domain'].'/';
+ $url = sprintf(
+ 'http://%s.%s/',
+ $domain ?: basename(getcwd()),
+ Configuration::get('domain')
+ );
- passthru('xdg-open '.escapeshellarg($url));
+ passthru('xdg-open ' . escapeshellarg($url));
})->descriptions('Open the site for the current (or specified) directory in your browser');
/**
* Generate a publicly accessible URL for your project.
*/
$app->command('share', function () {
- warning(
+ Writer::warn(
'It looks like you are running `cli/valet.php` directly,
please use the `valet` script in the project root instead.'
);
@@ -768,7 +830,7 @@
* Echo the currently tunneled URL.
*/
$app->command('fetch-share-url', function () {
- output(Ngrok::currentTunnelUrl());
+ echo Ngrok::currentTunnelUrl();
})->descriptions('Get the URL to the current Ngrok tunnel');
/**
@@ -776,9 +838,13 @@
*/
$app->command('ngrok-auth [authtoken]', function ($authtoken) {
if (!$authtoken) {
- throw new NgrokException('Missing arguments to authenticate ngrok. Use: "valet ngrok-auth [authtoken]"');
+ Writer::error('Please provide ngrok auth token');
+ return;
}
+
Ngrok::setAuthToken($authtoken);
+
+ Writer::info('Ngrok authentication token set.');
})->descriptions('Set authentication token for ngrok');
}
diff --git a/cli/includes/helpers.php b/cli/includes/helpers.php
index 0c0782b..c6a42c1 100644
--- a/cli/includes/helpers.php
+++ b/cli/includes/helpers.php
@@ -5,9 +5,6 @@
use Exception;
use Illuminate\Container\Container;
use Illuminate\Contracts\Container\BindingResolutionException;
-use Symfony\Component\Console\Helper\Table;
-use Symfony\Component\Console\Output\ConsoleOutput;
-use Symfony\Component\Console\Output\OutputInterface;
/**
* Define constants.
@@ -15,18 +12,19 @@
if (! defined('VALET_HOME_PATH')) {
if (testing()) {
define('VALET_HOME_PATH', __DIR__.'/../../tests/config/valet');
+ define('OLD_VALET_HOME_PATH', __DIR__.'/../../tests/old-config/valet');
+
} else {
define('VALET_HOME_PATH', $_SERVER['HOME'].'/.config/valet');
+ define('OLD_VALET_HOME_PATH', $_SERVER['HOME'].'/.valet');
}
}
-define('OLD_VALET_HOME_PATH', $_SERVER['HOME'].'/.valet');
if (! defined('VALET_STATIC_PREFIX')) {
define('VALET_STATIC_PREFIX', '41c270e4-5535-4daa-b23e-c269744c2f45');
}
define('VALET_LOOPBACK', '127.0.0.1');
-define('VALET_ROOT_PATH', realpath(__DIR__.'/../../')); //TODO: Check if it is in user
-define('VALET_BIN_PATH', realpath(__DIR__.'/../../bin/')); //TODO: Check if it is in user
+define('VALET_ROOT_PATH', realpath(__DIR__.'/../../'));
define('VALET_SERVER_PATH', realpath(__DIR__.'/../../server.php'));
define('ISOLATED_PHP_VERSION', 'ISOLATED_PHP_VERSION');
@@ -38,66 +36,6 @@ function testing(): bool
return strpos($_SERVER['SCRIPT_NAME'], 'phpunit') !== false;
}
-/**
- * Set or get a global console writer.
- * @throws BindingResolutionException
- */
-function writer(?OutputInterface $writer = null): ?OutputInterface
-{
- $container = Container::getInstance();
-
- if (! $writer) {
- if (! $container->bound('writer')) {
- $container->instance('writer', new ConsoleOutput());
- }
-
- return $container->make('writer');
- }
-
- $container->instance('writer', $writer);
-
- return null;
-}
-/**
- * Output the given text to the console.
- */
-function info(string $output): void
-{
- output(''.$output.'');
-}
-
-/**
- * Output the given text to the console.
- */
-function warning(string $output): void
-{
- output(''.$output.'>');
-}
-
-/**
- * Output a table to the console.
- */
-function table(array $headers = [], array $rows = []): void
-{
- $table = new Table(new ConsoleOutput());
-
- $table->setHeaders($headers)->setRows($rows);
-
- $table->render();
-}
-
-/**
- * Output the given text to the console.
- */
-function output(string $output): void
-{
- if (isset($_ENV['APP_ENV']) && $_ENV['APP_ENV'] === 'testing') {
- return;
- }
-
- (new ConsoleOutput())->writeln($output);
-}
-
if (!function_exists('resolve')) {
/**
* Resolve the given class from the container.
@@ -110,26 +48,6 @@ function resolve(string $class)
}
}
-if (!function_exists('endsWith')) {
- /**
- * Determine if a given string ends with a given substring.
- */
- function endsWith(string $haystack, string $needle): bool
- {
- return substr($haystack, -strlen($needle)) === $needle;
- }
-}
-
-if (!function_exists('startsWith')) {
- /**
- * Determine if a given string starts with a given substring.
- */
- function startsWith(string $haystack, string $needle): bool
- {
- return $needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0;
- }
-}
-
/**
* Swap the given class implementation in the container.
* @param mixed $instance
@@ -183,7 +101,7 @@ function tap($value, callable $callback)
/**
* Get the user.
*/
-function user(): string // TODO: Validate user function
+function user(): string
{
if (!isset($_SERVER['SUDO_USER'])) {
return $_SERVER['USER'];
diff --git a/cli/includes/require-drivers.php b/cli/includes/require-drivers.php
index 97f17d6..0f1547a 100644
--- a/cli/includes/require-drivers.php
+++ b/cli/includes/require-drivers.php
@@ -1,10 +1,14 @@
| "$CONFIG"
+ fi
+ fi
+}
+
+if [[ "$1" = "update" ]]
+then
+ if [[ "$2" ]]
+ then
+ composer global update "genesisweb/valet-linux-plus:$2"
+ else
+ composer global update "genesisweb/valet-linux-plus"
+ fi
+ valet install
+fi
+
+fix-config
diff --git a/cli/stubs/proxy.valet.conf b/cli/stubs/proxy.valet.conf
index e4e727f..7dd4ea2 100644
--- a/cli/stubs/proxy.valet.conf
+++ b/cli/stubs/proxy.valet.conf
@@ -18,8 +18,6 @@ server {
access_log off;
error_log "VALET_HOME_PATH/Log/VALET_SITE-error.log";
- error_page 404 "VALET_SERVER_PATH";
-
location / {
proxy_pass VALET_PROXY_HOST;
proxy_set_header Host $host;
diff --git a/cli/stubs/secure.isolated.valet.conf b/cli/stubs/secure.isolated.valet.conf
index d202cb9..9b0880a 100644
--- a/cli/stubs/secure.isolated.valet.conf
+++ b/cli/stubs/secure.isolated.valet.conf
@@ -9,11 +9,11 @@ server {
server {
listen VALET_HTTPS_PORT ssl http2;
+ listen 88;
server_name VALET_SITE www.VALET_SITE *.VALET_SITE;
root /;
charset utf-8;
client_max_body_size 128M;
- http2_push_preload on;
location /VALET_STATIC_PREFIX/ {
internal;
diff --git a/cli/stubs/secure.proxy.valet.conf b/cli/stubs/secure.proxy.valet.conf
index b8e8241..bfee1c1 100644
--- a/cli/stubs/secure.proxy.valet.conf
+++ b/cli/stubs/secure.proxy.valet.conf
@@ -8,12 +8,12 @@ server {
server {
listen VALET_HTTPS_PORT ssl http2;
+ listen 88;
#listen VALET_LOOPBACK:443 ssl http2; # valet loopback
server_name VALET_SITE www.VALET_SITE *.VALET_SITE;
root /;
charset utf-8;
client_max_body_size 128M;
- http2_push_preload on;
location /VALET_STATIC_PREFIX/ {
internal;
diff --git a/cli/valet.php b/cli/valet.php
index af259cc..cbf99e7 100644
--- a/cli/valet.php
+++ b/cli/valet.php
@@ -1,13 +1,17 @@
#!/usr/bin/env php
run();
} catch (Exception $e) {
- warning($e->getMessage());
+ Writer::error($e->getMessage());
}
diff --git a/composer.json b/composer.json
index 202692a..5a2f7c3 100644
--- a/composer.json
+++ b/composer.json
@@ -13,10 +13,6 @@
],
"license": "MIT",
"authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylorotwell@gmail.com"
- },
{
"name": "Uttam Rabadiya",
"email": "mail@uttam.dev"
@@ -44,19 +40,24 @@
"ext-pdo": "*",
"ext-posix": "*",
"ext-json": "*",
- "php": "^7.1|^7.2|^7.3||^8.0|^8.1|^8.2|^8.3",
- "illuminate/container": "~5.1|^6.0|^7.0|^8.0|^9.0|^10.0",
- "mnapoli/silly": "~1.1",
- "symfony/process": "^3.0|^4.0|^5.0|^6.0",
- "nategood/httpful": "~0.2",
- "tightenco/collect": "^5.3|^6.0|^7.0|^8.0"
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "php": "^8.2|^8.3",
+ "illuminate/container": "^11.0",
+ "mnapoli/silly": "^1.9",
+ "symfony/process": "^7.0",
+ "nategood/httpful": "^0.3",
+ "illuminate/collections": "^11.4",
+ "uttamrabadiya/console-components": "^1.0.1"
},
"require-dev": {
"mockery/mockery": "^1.2.3",
"phpunit/phpunit": "~5.5|^9.0",
"phpstan/phpstan": "^1.4",
"squizlabs/php_codesniffer": "^3.9",
- "friendsofphp/php-cs-fixer": "^3.3"
+ "friendsofphp/php-cs-fixer": "^3.3",
+ "symfony/var-dumper": "^7.0",
+ "ext-zip": "*"
},
"scripts": {
"post-install-cmd": [
diff --git a/php b/php
new file mode 100755
index 0000000..b93471c
--- /dev/null
+++ b/php
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+set -e
+
+DEFAULT_PHP_BINARY="/usr/bin/php"
+CONFIG_FILE="$HOME/.config/valet/config.json"
+
+# Check if the JSON file exists
+if [ -f "$CONFIG_FILE" ]; then
+ IS_PWD_MATCHED=false
+
+ # Read the paths array from the JSON file
+ paths=$(jq -r '.paths | .[]' "$CONFIG_FILE")
+
+ # Loop through the paths and print each one
+ for path in $paths
+ do
+ if [[ "$PWD" == "$path"* ]]; then
+ IS_PWD_MATCHED=true
+ break
+ fi
+ done
+ if [ $IS_PWD_MATCHED == true ]; then
+ SITE_NAME=$( basename $PWD );
+ SELECTED_PHP=$DEFAULT_PHP_BINARY
+ if jq -e '.isolated_versions | length > 0' "$CONFIG_FILE" >/dev/null; then
+ if [ "$(jq -r ".isolated_versions[\"$SITE_NAME\"]" "$CONFIG_FILE")" != "null" ]; then
+ SELECTED_PHP=$(jq -r ".isolated_versions[\"$SITE_NAME\"]" "$CONFIG_FILE")
+ elif [ "$(jq -r ".fallback_binary" "$CONFIG_FILE")" != "null" ]; then
+ SELECTED_PHP=$(jq -r ".fallback_binary" "$CONFIG_FILE")
+ fi
+ fi
+ else
+ SELECTED_PHP=$(jq -r ".fallback_binary" "$CONFIG_FILE")
+ fi
+else
+ SELECTED_PHP=$DEFAULT_PHP_BINARY
+fi
+
+if ! [ -f "$SELECTED_PHP" ]; then
+ SELECTED_PHP=$DEFAULT_PHP_BINARY
+fi
+
+# shellcheck disable=SC2145
+eval "$SELECTED_PHP ${@@Q}"
diff --git a/phpstan-errors b/phpstan-errors
deleted file mode 100644
index bb81b2b..0000000
--- a/phpstan-errors
+++ /dev/null
@@ -1,410 +0,0 @@
- ------ ----------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Configuration.php
- ------ ----------------------------------------------------------------------------------------------------------------
- 124 Parameter #1 $config of method Valet\Configuration::write() expects array, mixed given.
- 127 Unable to resolve the template type TKey in call to function collect
- 💡 See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type
- 127 Unable to resolve the template type TValue in call to function collect
- 💡 See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type
- 144 Parameter #1 $config of method Valet\Configuration::write() expects array, mixed given.
- 145 Unable to resolve the template type TKey in call to function collect
- 💡 See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type
- 145 Unable to resolve the template type TValue in call to function collect
- 💡 See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type
- 160 Parameter #1 $config of method Valet\Configuration::write() expects array, mixed given.
- 161 Unable to resolve the template type TKey in call to function collect
- 💡 See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type
- 161 Unable to resolve the template type TValue in call to function collect
- 💡 See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type
- 162 Parameter #1 $path of method Valet\Filesystem::isDir() expects string, mixed given.
- 170 Method Valet\Configuration::read() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 172 Method Valet\Configuration::read() should return array but returns mixed.
- 203 Method Valet\Configuration::updateKey() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 205 Method Valet\Configuration::updateKey() should return array but returns mixed.
- 214 Method Valet\Configuration::write() has parameter $config with no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 236 Part $domain (mixed) of encapsed string cannot be cast to string.
- 237 Parameter #3 ...$values of function sprintf expects bool|float|int|string|null, mixed given.
- ------ ----------------------------------------------------------------------------------------------------------------
-
- ------ ----------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Drivers/Specific/CraftValetDriver.php
- ------ ----------------------------------------------------------------------------------------------------------------------------
- 20 Method Valet\Drivers\Specific\CraftValetDriver::frontControllerDirectory() has parameter $sitePath with no type specified.
- ------ ----------------------------------------------------------------------------------------------------------------------------
-
- ------ -------------------------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Drivers/Specific/DrupalValetDriver.php
- ------ -------------------------------------------------------------------------------------------------------------------------------------------
- 37 Offset 'extension' does not exist on array{dirname?: string, basename: string, extension?: string, filename: string}.
- 76 Method Valet\Drivers\Specific\DrupalValetDriver::addSubdirectory() has parameter $sitePath with no type specified.
- 98 Method Valet\Drivers\Specific\DrupalValetDriver::possibleSubdirectories() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- ------ -------------------------------------------------------------------------------------------------------------------------------------------
-
- ------ -------------------------------------------------
- Line cli/Valet/Drivers/Specific/KirbyValetDriver.php
- ------ -------------------------------------------------
- 57 Variable $indexPath might not be defined.
- ------ -------------------------------------------------
-
- ------ -----------------------------------------------------------------------------------------
- Line cli/Valet/Drivers/Specific/Magento2ValetDriver.php
- ------ -----------------------------------------------------------------------------------------
- 28 Parameter #1 $haystack of function strpos expects string, string|null given.
- 29 Parameter #3 $subject of function preg_replace expects array|string, string|null given.
- 34 Parameter #1 $haystack of function strpos expects string, string|null given.
- ------ -----------------------------------------------------------------------------------------
-
- ------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Drivers/Specific/StatamicV2ValetDriver.php
- ------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- 76 Variable $indexPath might not be defined.
- 97 Method Valet\Drivers\Specific\StatamicV2ValetDriver::getLocales() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 125 Cannot access offset 'path' on array{scheme?: string, host?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false.
- ------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
- ------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Drivers/Specific/StatamicValetDriver.php
- ------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- 29 Method Valet\Drivers\Specific\StatamicValetDriver::frontControllerPath() should return string but returns string|null.
- 35 Method Valet\Drivers\Specific\StatamicValetDriver::getStaticPath() has no return type specified.
- 44 Cannot access offset 'path' on array{scheme?: string, host?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false.
- ------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
- ------ ------------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Drivers/Specific/Typo3ValetDriver.php
- ------ ------------------------------------------------------------------------------------------------------------------------------
- 24 Property Valet\Drivers\Specific\Typo3ValetDriver::$documentRoot has no type specified.
- 36 Property Valet\Drivers\Specific\Typo3ValetDriver::$forbiddenUriPatterns has no type specified.
- 87 Parameter #1 $uri of method Valet\Drivers\Specific\Typo3ValetDriver::isAccessAuthorized() expects string, string|null given.
- ------ ------------------------------------------------------------------------------------------------------------------------------
-
- ------ ----------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Drivers/Specific/WordPressValetDriver.php
- ------ ----------------------------------------------------------------------------------------------------------------------
- 25 Parameter #3 $uri of method Valet\Drivers\BasicValetDriver::frontControllerPath() expects string, string|null given.
- 32 Method Valet\Drivers\Specific\WordPressValetDriver::forceTrailingSlash() has parameter $uri with no type specified.
- ------ ----------------------------------------------------------------------------------------------------------------------
-
- ------ ---------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Drivers/ValetDriver.php
- ------ ---------------------------------------------------------------------------------------------------------------------
- 55 Instantiated class LocalValetDriver not found.
- 💡 Learn more at https://phpstan.org/user-guide/discovering-symbols
- 86 Method Valet\Drivers\ValetDriver::driversIn() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 99 Cannot access offset 0 on mixed.
- 101 Cannot access offset 0 on mixed.
- 101 Parameter #1 $path of function basename expects string, mixed given.
- 110 Method Valet\Drivers\ValetDriver::specificDrivers() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 120 Method Valet\Drivers\ValetDriver::customDrivers() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 213 Parameter #1 $json of function json_decode expects string, string|false given.
- 219 Cannot access offset 'require' on mixed.
- 219 Cannot access offset string on mixed.
- ------ ---------------------------------------------------------------------------------------------------------------------
-
- ------ ----------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Facades/Facade.php
- ------ ----------------------------------------------------------------------------------------------------------------------------
- 26 Method Valet\Facades\Facade::__callStatic() has parameter $parameters with no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 30 Parameter #1 $callback of function call_user_func_array expects callable(): mixed, array{mixed, string} given.
- ------ ----------------------------------------------------------------------------------------------------------------------------
-
- ------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Filesystem.php
- ------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------
- 130 Method Valet\Filesystem::get() should return string but returns string|false.
- 144 Method Valet\Filesystem::put() should return string but returns int<0, max>|false.
- 335 Method Valet\Filesystem::realpath() should return string but returns string|false.
- 357 Parameter #1 $filename of function is_link expects string, string|false given.
- 358 Parameter #1 $path of function readlink expects string, string|false given.
- 361 Method Valet\Filesystem::readLink() should return string but returns string|false.
- 393 Method Valet\Filesystem::scandir() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 395 Parameter #1 $value of function collect expects Illuminate\Contracts\Support\Arrayable|iterable|null, array|false given.
- 406 Method Valet\Filesystem::toIterator() has parameter $files with no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 406 Method Valet\Filesystem::toIterator() return type has no value type specified in iterable type Traversable.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 408 Instanceof between array|string and Traversable will always evaluate to false.
- 💡 Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your phpstan.neon.
- ------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------
-
- ------ -------------------------------------------------------------------
- Line cli/Valet/Mailpit.php
- ------ -------------------------------------------------------------------
- 71 Part $domain (mixed) of encapsed string cannot be cast to string.
- 72 Part $domain (mixed) of encapsed string cannot be cast to string.
- 177 Part $domain (mixed) of encapsed string cannot be cast to string.
- ------ -------------------------------------------------------------------
-
- ------ ----------------------------------------------------------------------------------------------------------
- Line cli/Valet/Mysql.php
- ------ ----------------------------------------------------------------------------------------------------------
- 49 Access to an undefined property Valet\Contracts\PackageManager::$mysqlPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 50 Access to an undefined property Valet\Contracts\PackageManager::$mysqlPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 52 Access to an undefined property Valet\Contracts\PackageManager::$mariaDBPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 53 Access to an undefined property Valet\Contracts\PackageManager::$mariaDBPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 60 Method Valet\Mysql::install() has no return type specified.
- 60 Method Valet\Mysql::install() has parameter $useMariaDB with no type specified.
- 65 Access to an undefined property Valet\Contracts\PackageManager::$mariaDBPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 65 Access to an undefined property Valet\Contracts\PackageManager::$mysqlPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 73 Access to an undefined property Valet\Contracts\PackageManager::$mariaDBPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 74 Access to an undefined property Valet\Contracts\PackageManager::$mysqlPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 80 Access to an undefined property Valet\Contracts\PackageManager::$mysqlPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 81 Access to an undefined property Valet\Contracts\PackageManager::$mariaDBPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 235 Cannot call method execute() on bool|PDOStatement|null.
- 237 Cannot call method rowCount() on bool|PDOStatement|null.
- 243 Method Valet\Mysql::exportDatabase() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 318 Method Valet\Mysql::getDatabases() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 326 Cannot call method fetchAll() on PDOStatement|true.
- 339 Method Valet\Mysql::query() never returns void so it can be removed from the return type.
- 402 Method Valet\Mysql::getSystemDatabases() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 432 Access to an undefined property Valet\Contracts\PackageManager::$mariaDBPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 467 Method Valet\Mysql::getCredentials() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- ------ ----------------------------------------------------------------------------------------------------------
-
- ------ ---------------------------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Nginx.php
- ------ ---------------------------------------------------------------------------------------------------------------------------------------------
- 144 Method Valet\Nginx::configuredSites() return type with generic class Illuminate\Support\Collection does not specify its types: TKey, TValue
- 💡 You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your phpstan.neon.
- ------ ---------------------------------------------------------------------------------------------------------------------------------------------
-
- ------ ---------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Ngrok.php
- ------ ---------------------------------------------------------------------------------------------------------------------
- 36 Method Valet\Ngrok::currentTunnelUrl() should return string|null but returns mixed.
- 61 Method Valet\Ngrok::findHttpTunnelUrl() has parameter $tunnels with no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- ------ ---------------------------------------------------------------------------------------------------------------------
-
- ------ -------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/PackageManagers/Apt.php
- ------ -------------------------------------------------------------------------------------------------------------------------
- 37 Constant Valet\PackageManagers\Apt::PHP_FPM_PATTERN_BY_VERSION type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 51 Method Valet\PackageManagers\Apt::packages() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 119 Offset string on array{} in empty() does not exist.
- 120 Offset string does not exist on array{}.
- ------ -------------------------------------------------------------------------------------------------------------------------
-
- ------ -------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/PackageManagers/Dnf.php
- ------ -------------------------------------------------------------------------------------------------------------------------
- 37 Constant Valet\PackageManagers\Dnf::PHP_FPM_PATTERN_BY_VERSION type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 113 Offset string on array{} in empty() does not exist.
- 114 Offset string does not exist on array{}.
- ------ -------------------------------------------------------------------------------------------------------------------------
-
- ------ ---------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/PackageManagers/Eopkg.php
- ------ ---------------------------------------------------------------------------------------------------------------------------
- 37 Constant Valet\PackageManagers\Eopkg::PHP_FPM_PATTERN_BY_VERSION type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 51 Method Valet\PackageManagers\Eopkg::packages() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 119 Offset string on array{} in empty() does not exist.
- 120 Offset string does not exist on array{}.
- ------ ---------------------------------------------------------------------------------------------------------------------------
-
- ------ --------------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/PackageManagers/PackageKit.php
- ------ --------------------------------------------------------------------------------------------------------------------------------
- 37 Constant Valet\PackageManagers\PackageKit::PHP_FPM_PATTERN_BY_VERSION type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 51 Method Valet\PackageManagers\PackageKit::packages() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 119 Offset string on array{} in empty() does not exist.
- 120 Offset string does not exist on array{}.
- ------ --------------------------------------------------------------------------------------------------------------------------------
-
- ------ ----------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/PackageManagers/Pacman.php
- ------ ----------------------------------------------------------------------------------------------------------------------------
- 37 Constant Valet\PackageManagers\Pacman::PHP_FPM_PATTERN_BY_VERSION type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 51 Method Valet\PackageManagers\Pacman::packages() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 122 Offset string on array{} in empty() does not exist.
- 123 Offset string does not exist on array{}.
- 126 Parameter #2 $replace of function str_replace expects array|string, string|null given.
- 136 Parameter #2 $replace of function str_replace expects array|string, string|null given.
- ------ ----------------------------------------------------------------------------------------------------------------------------
-
- ------ -------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/PackageManagers/Yum.php
- ------ -------------------------------------------------------------------------------------------------------------------------
- 37 Constant Valet\PackageManagers\Yum::PHP_FPM_PATTERN_BY_VERSION type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 113 Offset string on array{} in empty() does not exist.
- 114 Offset string does not exist on array{}.
- ------ -------------------------------------------------------------------------------------------------------------------------
-
- ------ --------------------------------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/PhpFpm.php
- ------ --------------------------------------------------------------------------------------------------------------------------------------------------
- 16 Property Valet\PhpFpm::$config has no type specified.
- 17 Property Valet\PhpFpm::$pm has no type specified.
- 18 Property Valet\PhpFpm::$sm has no type specified.
- 19 Property Valet\PhpFpm::$cli has no type specified.
- 20 Property Valet\PhpFpm::$files has no type specified.
- 21 Property Valet\PhpFpm::$site has no type specified.
- 22 Property Valet\PhpFpm::$nginx has no type specified.
- 147 Method Valet\PhpFpm::restart() has parameter $version with no type specified.
- 155 Method Valet\PhpFpm::stop() has parameter $version with no type specified.
- 163 Method Valet\PhpFpm::status() has parameter $version with no type specified.
- 218 Method Valet\PhpFpm::isolatedDirectories() return type with generic class Illuminate\Support\Collection does not specify its types: TKey, TValue
- 💡 You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your phpstan.neon.
- 243 Method Valet\PhpFpm::normalizePhpVersion() has parameter $version with no type specified.
- 398 Method Valet\PhpFpm::utilizedPhpVersions() should return array but returns array.
- 455 Method Valet\PhpFpm::getExtensionPrefix() has parameter $version with no type specified.
- ------ --------------------------------------------------------------------------------------------------------------------------------------------------
-
- ------ ---------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Server.php
- ------ ---------------------------------------------------------------------------------------------------------------
- 10 Property Valet\Server::$config type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 12 Method Valet\Server::__construct() has parameter $config with no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 36 Method Valet\Server::show404() has no return type specified.
- 46 Method Valet\Server::showDirectoryListing() has no return type specified.
- 57 Parameter #1 $array of function usort expects TArray of array, array|false given.
- 69 Parameter #2 $array of function array_map expects array, array|false given.
- 80 Method Valet\Server::hostIsIpAddress() should return bool but returns int|false.
- 148 Parameter #1 $haystack of function strpos expects string, string|null given.
- 149 Parameter #2 $string of function explode expects string, string|null given.
- 152 Method Valet\Server::allowWildcardDnsDomains() should return string but returns string|null.
- ------ ---------------------------------------------------------------------------------------------------------------
-
- ------ ---------------------------------------------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/ServiceManagers/LinuxService.php
- ------ ---------------------------------------------------------------------------------------------------------------------------------------------------------------
- 175 Method Valet\ServiceManagers\LinuxService::getRealService() should return string but returns mixed.
- 175 Parameter #1 $value of function collect expects Illuminate\Contracts\Support\Arrayable<(int|string), mixed>|iterable<(int|string), mixed>|null, string given.
- 175 Unable to resolve the template type TKey in call to function collect
- 💡 See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type
- 175 Unable to resolve the template type TValue in call to function collect
- 💡 See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type
- ------ ---------------------------------------------------------------------------------------------------------------------------------------------------------------
-
- ------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/ServiceManagers/Systemd.php
- ------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- 181 Method Valet\ServiceManagers\Systemd::getRealService() should return string but returns mixed.
- 181 Parameter #1 $value of function collect expects Illuminate\Contracts\Support\Arrayable<(int|string), mixed>|iterable<(int|string), mixed>|null, string given.
- 181 Unable to resolve the template type TKey in call to function collect
- 💡 See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type
- 181 Unable to resolve the template type TValue in call to function collect
- 💡 See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type
- 182 Parameter #1 $callback of method Illuminate\Support\Collection<(int|string),mixed>::first() expects (callable(mixed, int|string): bool)|null, Closure(mixed): (int<0, max>|false) given.
- 183 Part $service (mixed) of encapsed string cannot be cast to string.
- ------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
- ------ --------------------------------------------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Site.php
- ------ --------------------------------------------------------------------------------------------------------------------------------------------------
- 71 Method Valet\Site::links() return type with generic class Illuminate\Support\Collection does not specify its types: TKey, TValue
- 💡 You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your phpstan.neon.
- 85 Method Valet\Site::proxies() return type with generic class Illuminate\Support\Collection does not specify its types: TKey, TValue
- 💡 You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your phpstan.neon.
- 108 Possibly invalid array key type (array|string).
- 219 Parameter #3 $subject of function str_replace expects array|string, mixed given.
- 220 Parameter #1 $url of method Valet\Site::getNginxConf() expects string, mixed given.
- 222 Parameter #1 $search of function str_replace expects array|string, mixed given.
- 225 Parameter #1 $url of method Valet\Site::unsecure() expects string, mixed given.
- 237 Method Valet\Site::secured() return type with generic class Illuminate\Support\Collection does not specify its types: TKey, TValue
- 💡 You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your phpstan.neon.
- 294 Parameter #1 $url of method Valet\Site::createSecureNginxServer() expects string, mixed given.
- 306 Parameter #1 $path of method Valet\Site::host() expects string, string|false given.
- 536 Method Valet\Site::getCertificates() return type with generic class Illuminate\Support\Collection does not specify its types: TKey, TValue
- 💡 You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your phpstan.neon.
- 550 Method Valet\Site::getLinks() has parameter $certs with generic class Illuminate\Support\Collection but does not specify its types: TKey, TValue
- 💡 You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your phpstan.neon.
- 550 Method Valet\Site::getLinks() return type with generic class Illuminate\Support\Collection does not specify its types: TKey, TValue
- 💡 You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your phpstan.neon.
- 728 Method Valet\Site::getSites() has parameter $certs with generic class Illuminate\Support\Collection but does not specify its types: TKey, TValue
- 💡 You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your phpstan.neon.
- 728 Method Valet\Site::getSites() return type with generic class Illuminate\Support\Collection does not specify its types: TKey, TValue
- 💡 You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your phpstan.neon.
- 775 Method Valet\Site::parked() return type with generic class Illuminate\Support\Collection does not specify its types: TKey, TValue
- 💡 You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your phpstan.neon.
- ------ --------------------------------------------------------------------------------------------------------------------------------------------------
-
- ------ -------------------------------------------------------------------------------------------------------------
- Line cli/Valet/Valet.php
- ------ -------------------------------------------------------------------------------------------------------------
- 36 Property Valet\Valet::$phpBin has no type specified.
- 212 Method Valet\Valet::getRunningFpmVersions() return type has no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- 241 Method Valet\Valet::getAvailablePackageManager() should return class-string but returns string.
- 249 Cannot call method isAvailable() on mixed.
- 269 Method Valet\Valet::getAvailableServiceManager() should return class-string but returns string.
- 273 Cannot call method isAvailable() on mixed.
- ------ -------------------------------------------------------------------------------------------------------------
-
- ------ --------------------------------------------------------------------------------------
- Line cli/Valet/ValetRedis.php
- ------ --------------------------------------------------------------------------------------
- 47 Access to an undefined property Valet\Contracts\PackageManager::$redisPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 48 Access to an undefined property Valet\Contracts\PackageManager::$redisPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 56 Access to an undefined property Valet\Contracts\PackageManager::$redisPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 64 Access to an undefined property Valet\Contracts\PackageManager::$redisPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- 72 Access to an undefined property Valet\Contracts\PackageManager::$redisPackageName.
- 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
- ------ --------------------------------------------------------------------------------------
-
- ------ -------------------------------------------------------------------------------------------------------------------
- Line cli/app.php
- ------ -------------------------------------------------------------------------------------------------------------------
- 338 Parameter #1 $path of function basename expects string, string|false given.
- 338 Parameter #1 $sitePath of static method Valet\Drivers\ValetDriver::assign() expects string, string|false given.
- 446 Parameter #1 $path of function basename expects string, string|false given.
- 446 Parameter #1 $target of static method Valet\Facades\Site::link() expects string, string|false given.
- 455 Parameter #1 $path of function basename expects string, string|false given.
- 473 Parameter #1 $path of static method Valet\Facades\Site::host() expects string, string|false given.
- 486 Parameter #1 $path of static method Valet\Facades\Site::host() expects string, string|false given.
- 499 Parameter #1 $path of static method Valet\Facades\Site::host() expects string, string|false given.
- 731 Parameter #1 $site of static method Valet\Facades\Configuration::parseDomain() expects string, string|null given.
- 735 Parameter #1 $path of function basename expects string, string|false given.
- 771 Parameter #1 $path of static method Valet\Facades\Site::host() expects string, string|false given.
- ------ -------------------------------------------------------------------------------------------------------------------
-
- ------ -----------------------------------------------------------------------------------------------------------------------
- Line cli/includes/helpers.php
- ------ -----------------------------------------------------------------------------------------------------------------------
- 127 Function Valet\strArrayReplace() has parameter $searchAndReplace with no value type specified in iterable type array.
- 💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
- ------ -----------------------------------------------------------------------------------------------------------------------
-
- [ERROR] Found 183 errors
-
diff --git a/phpstan.neon b/phpstan.neon
index 165952f..4ed4682 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -3,3 +3,5 @@ parameters:
paths:
- cli
- tests
+ ignoreErrors:
+ - '#Method .*? with no value type specified in iterable type array#'
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..2aa1809
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+ cli/Valet
+
+
+
+
+ ./tests
+
+
+
+
+
+
diff --git a/server.php b/server.php
index 852f975..fcdd946 100644
--- a/server.php
+++ b/server.php
@@ -41,7 +41,7 @@
$siteName = $server->siteNameFromHttpHost($_SERVER['HTTP_HOST']);
$valetSitePath = $server->sitePath($siteName);
-if (is_null($valetSitePath) && is_null($valetSitePath = $server->defaultSitePath())) {
+if ($valetSitePath === null && is_null($valetSitePath = $server->defaultSitePath())) {
Server::show404();
}
diff --git a/tests/CliTest.php b/tests/CliTest.php
new file mode 100644
index 0000000..8e26127
--- /dev/null
+++ b/tests/CliTest.php
@@ -0,0 +1,1198 @@
+tester->run(['command' => 'domain']);
+
+ $this->tester->assertCommandIsSuccessful();
+ $domain = ConfigurationFacade::get('domain');
+
+ $this->assertSame('test', $domain);
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString('Your current Valet domain is [test]', $output->fetch());
+ }
+
+ /**
+ * @test
+ */
+ public function itWillSetDomainInConfig(): void
+ {
+ Writer::fake();
+ $dnsmasq = Mockery::mock(DnsMasq::class);
+ $dnsmasq->shouldReceive('updateDomain')->once()->with('localhost');
+ swap(DnsMasq::class, $dnsmasq);
+
+ $config = Mockery::mock(Configuration::class);
+ $config->shouldReceive('get')->with('domain')->andReturn('test')->once();
+ $config->shouldReceive('set')->with('domain', 'localhost')->once();
+ swap(Configuration::class, $config);
+
+ $siteSecure = Mockery::mock(SiteSecure::class);
+ $siteSecure->shouldReceive('reSecureForNewDomain')->with('test', 'localhost')->once();
+ swap(SiteSecure::class, $siteSecure);
+
+ $phpFpm = Mockery::mock(PhpFpm::class);
+ $phpFpm->shouldReceive('restart')->withNoArgs()->once();
+ swap(PhpFpm::class, $phpFpm);
+
+ $nginx = Mockery::mock(Nginx::class);
+ $nginx->shouldReceive('restart')->withNoArgs()->once();
+ swap(Nginx::class, $nginx);
+
+ $this->tester->run(['command' => 'domain', 'domain' => 'localhost']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString('Your Valet domain has been updated to [localhost]', $output->fetch());
+ }
+
+ /**
+ * @test
+ */
+ public function itWillReadNginxPortFromConfig(): void
+ {
+ Writer::fake();
+
+ $this->tester->run(['command' => 'port']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString('Current Nginx port (HTTP): 80', $content);
+ $this->assertStringContainsString('Current Nginx port (HTTPS): 443', $content);
+ }
+
+ public function nginxPortDataProvider(): array
+ {
+ return [
+ [8443, true, 'https_port', 'Your Nginx HTTPS port has been updated to [8443]'],
+ [88, false, 'port', 'Your Nginx HTTP port has been updated to [88]'],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider nginxPortDataProvider
+ */
+ public function itWillUpdateNginxPortSuccessfully(
+ int $port,
+ bool $isHttps,
+ string $updateKey,
+ string $expectedOutput
+ ): void {
+ Writer::fake();
+
+ $nginx = Mockery::mock(Nginx::class);
+ $nginx->shouldReceive('restart')->withNoArgs()->once();
+
+ if ($isHttps === false) {
+ $nginx->shouldReceive('updatePort')->with($port)->once();
+ }
+
+ swap(Nginx::class, $nginx);
+
+ $config = Mockery::mock(Configuration::class);
+ $config->shouldReceive('set')->with($updateKey, $port)->once();
+ swap(Configuration::class, $config);
+
+ $siteSecure = Mockery::mock(SiteSecure::class);
+ $siteSecure->shouldReceive('regenerateSecuredSitesConfig')->withNoArgs()->once();
+ swap(SiteSecure::class, $siteSecure);
+
+ $phpFpm = Mockery::mock(PhpFpm::class);
+ $phpFpm->shouldReceive('restart')->withNoArgs()->once();
+ swap(PhpFpm::class, $phpFpm);
+
+ $this->tester->run(['command' => 'port', 'port' => $port, '--https' => $isHttps]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString($expectedOutput, $output->fetch());
+ }
+
+ /**
+ * @test
+ */
+ public function itWillValidateWhichCommand(): void
+ {
+ Writer::fake();
+
+ $this->tester->run(['command' => 'which']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString('This site is served by [Valet\Drivers\BasicValetDriver]', $output->fetch());
+ }
+
+ public function parkedDirectoryProvider(): array
+ {
+ return [
+ ['/test/directory', '/test/directory'],
+ [null, getcwd()],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider parkedDirectoryProvider
+ */
+ public function itWillParkDirectoryToConfig(?string $directory, string $expectedDirectory): void
+ {
+ Writer::fake();
+
+ $config = Mockery::mock(Configuration::class);
+ $config->shouldReceive('addPath')->with($expectedDirectory)->once();
+ swap(Configuration::class, $config);
+
+ $this->tester->run(['command' => 'park', 'path' => $directory]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString(
+ \sprintf('The [%s] directory has been added to Valet\'s paths.', $expectedDirectory),
+ $output->fetch()
+ );
+ }
+
+ public function directoryProvider(): array
+ {
+ return [
+ [null, 'No paths have been registered.'],
+ ['/test/directory', '/test/directory'],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider directoryProvider
+ */
+ public function itWillReadParkedDirectories(?string $path, string $expectedMessage): void
+ {
+ Writer::fake();
+
+ if ($path !== null) {
+ ConfigurationFacade::addPath($path);
+ }
+
+ $this->tester->run(['command' => 'paths']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString(
+ $expectedMessage,
+ $output->fetch()
+ );
+ }
+
+ /**
+ * @test
+ * @dataProvider parkedDirectoryProvider
+ */
+ public function itWillForgetParkedDirectory(?string $directory, string $expectedDirectory): void
+ {
+ Writer::fake();
+
+ $config = Mockery::mock(Configuration::class);
+ $config->shouldReceive('removePath')->with($expectedDirectory)->once();
+ swap(Configuration::class, $config);
+
+ $this->tester->run(['command' => 'forget', 'path' => $directory]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString(
+ \sprintf('The [%s] directory has been removed from Valet\'s paths.', $expectedDirectory),
+ $output->fetch()
+ );
+ }
+
+ public function nginxProxyDataProvider(): array
+ {
+ return [
+ [
+ 'mails',
+ 'http://localhost:8045',
+ true,
+ 'mails.test',
+ 'Valet will now proxy [https://mails.test] traffic to [http://localhost:8045]'
+ ],
+ [
+ 'withtld.test',
+ 'http://localhost:8045',
+ true,
+ 'withtld.test',
+ 'Valet will now proxy [https://withtld.test] traffic to [http://localhost:8045]'
+ ],
+ [
+ 'mails',
+ 'http://localhost:8045',
+ false,
+ 'mails.test',
+ 'Valet will now proxy [http://mails.test] traffic to [http://localhost:8045]'
+ ]
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider nginxProxyDataProvider
+ */
+ public function itWillCreateNginxProxy(
+ string $domain,
+ string $host,
+ bool $isSecure,
+ string $expectedDomain,
+ string $expectedMessage
+ ): void {
+ Writer::fake();
+
+ $siteProxy = Mockery::mock(SiteProxy::class);
+ $siteProxy->shouldReceive('proxyCreate')->with($expectedDomain, $host, $isSecure)->once();
+ swap(SiteProxy::class, $siteProxy);
+
+ $nginx = Mockery::mock(Nginx::class);
+ $nginx->shouldReceive('restart')->withNoArgs()->once();
+ swap(Nginx::class, $nginx);
+
+ $this->tester->run(['command' => 'proxy', 'domain' => $domain, 'host' => $host, '--secure' => $isSecure]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString(
+ $expectedMessage,
+ $output->fetch()
+ );
+ }
+
+ public function invalidProxyDataProvider(): array
+ {
+ return [
+ [
+ [
+ 'domain' => null,
+ ],
+ 'Please provide domain'
+ ],
+ [
+ [
+ 'host' => null,
+ ],
+ 'Please provide host'
+ ],
+ [
+ [
+ 'host' => 'invalid-url',
+ ],
+ '"invalid-url" is not a valid URL'
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider invalidProxyDataProvider
+ */
+ public function itWillFailToProxyDomainWhenValidParametersNotAvailable(
+ array $overrides,
+ string $expectedMessage
+ ): void {
+ Writer::fake();
+
+ $domain = array_key_exists('domain', $overrides) ? $overrides['domain'] : 'mails';
+ $host = array_key_exists('host', $overrides) ? $overrides['host'] : 'http://127.0.0.1:8025';
+
+ $site = Mockery::mock(Site::class);
+ $site->shouldReceive('proxyDelete')->never();
+ swap(Site::class, $site);
+
+ $nginx = Mockery::mock(Nginx::class);
+ $nginx->shouldReceive('restart')->never();
+ swap(Nginx::class, $nginx);
+
+ $this->tester->run(['command' => 'proxy', 'domain' => $domain, 'host' => $host]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString(
+ $expectedMessage,
+ $output->fetch()
+ );
+ }
+
+ public function nginxUnproxyDataProvider(): array
+ {
+ return [
+ [
+ 'mails',
+ 'mails.test',
+ 'Valet will no longer proxy [mails.test]'
+ ],
+ [
+ 'withtld.test',
+ 'withtld.test',
+ 'Valet will no longer proxy [withtld.test]'
+ ]
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider nginxUnproxyDataProvider
+ */
+ public function itWillRemoveNginxProxy(string $domain, string $expectedDomain, string $expectedMessage): void
+ {
+ Writer::fake();
+
+ $siteSecure = Mockery::mock(SiteSecure::class);
+ $siteSecure->shouldReceive('unsecure')->with($expectedDomain)->once();
+ swap(SiteSecure::class, $siteSecure);
+
+ $nginx = Mockery::mock(Nginx::class);
+ $nginx->shouldReceive('restart')->withNoArgs()->once();
+ swap(Nginx::class, $nginx);
+
+ $this->tester->run(['command' => 'unproxy', 'domain' => $domain]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString(
+ $expectedMessage,
+ $output->fetch()
+ );
+ }
+
+ public function invalidUnproxyDataProvider(): array
+ {
+ return [
+ [
+ [
+ 'domain' => null,
+ ],
+ 'Please provide domain'
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider invalidUnproxyDataProvider
+ */
+ public function itWillFailToUnproxyDomainWhenValidParametersNotAvailable(
+ array $overrides,
+ string $expectedMessage
+ ): void {
+ Writer::fake();
+
+ $domain = array_key_exists('domain', $overrides) ? $overrides['domain'] : 'mails';
+
+ $site = Mockery::mock(Site::class);
+ $site->shouldReceive('proxyDelete')->never();
+ swap(Site::class, $site);
+
+ $nginx = Mockery::mock(Nginx::class);
+ $nginx->shouldReceive('restart')->never();
+ swap(Nginx::class, $nginx);
+
+ $this->tester->run(['command' => 'unproxy', 'domain' => $domain]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString(
+ $expectedMessage,
+ $output->fetch()
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillListProxies(): void
+ {
+ Writer::fake();
+
+ $siteProxies = Mockery::mock(SiteProxy::class);
+ $siteProxies->shouldReceive('proxies')->withNoArgs()->once()->andReturn(collect([
+ [
+ 'url' => 'https://mails.localhost',
+ 'secured' => '✓',
+ 'path' => 'http://127.0.0.1:8045',
+ ],
+ [
+ 'url' => 'http://docker-host.localhost',
+ 'secured' => '✕',
+ 'path' => 'http://127.0.0.1:8888',
+ ],
+ ]));
+ swap(SiteProxy::class, $siteProxies);
+
+ $this->tester->run(['command' => 'proxies']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ 'https://mails.localhost | ✓ | http://127.0.0.1:8045',
+ $content
+ );
+ $this->assertStringContainsString(
+ 'http://docker-host.localhost | ✕ | http://127.0.0.1:8888',
+ $content
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillLinkCwdToNginx(): void
+ {
+ Writer::fake();
+
+ $currentDirectory = getcwd();
+ $domainName = basename($currentDirectory);
+
+ $siteLink = Mockery::mock(SiteLink::class);
+ $siteLink->shouldReceive('link')
+ ->with($currentDirectory, $domainName)
+ ->once()
+ ->andReturn('direct-path');
+ swap(SiteLink::class, $siteLink);
+
+ $this->tester->run(['command' => 'link']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ 'A [' . $domainName . '] symbolic link has been created in [direct-path]',
+ $content
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUnlinkCwdToNginx(): void
+ {
+ Writer::fake();
+
+ $domainName = basename(getcwd());
+
+ $siteLink = Mockery::mock(SiteLink::class);
+ $siteLink->shouldReceive('unlink')->with($domainName)->once();
+ swap(SiteLink::class, $siteLink);
+
+ $this->tester->run(['command' => 'unlink']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ 'The [' . $domainName . '] symbolic link has been removed',
+ $content
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillListLinks(): void
+ {
+ Writer::fake();
+
+ $siteLink = Mockery::mock(SiteLink::class);
+ $siteLink->shouldReceive('links')->withNoArgs()->once()->andReturn(collect([
+ [
+ 'url' => 'http://scripts.test',
+ 'secured' => '✕',
+ 'path' => '/test/path',
+ ],
+ ]));
+ swap(SiteLink::class, $siteLink);
+
+ $this->tester->run(['command' => 'links']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ 'http://scripts.test | ✕ | /test/path',
+ $content
+ );
+ }
+
+ public function secureNginxDomainProvider(): array
+ {
+ return [
+ [
+ [
+ 'domain' => 'test-domain',
+ ],
+ 'The [test-domain.test] site has been secured with a fresh TLS certificate',
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider secureNginxDomainProvider
+ */
+ public function itWillSecureNginxDomain(array $overrides, string $expectedMessage): void
+ {
+ Writer::fake();
+
+ $domain = array_key_exists('domain', $overrides) ? $overrides['domain'] : 'test-domain';
+
+ $siteSecure = Mockery::mock(SiteSecure::class);
+ $siteSecure->shouldReceive('secure')->with($domain . '.test')->once();
+ swap(SiteSecure::class, $siteSecure);
+
+ $nginx = Mockery::mock(Nginx::class);
+ $nginx->shouldReceive('restart')->withNoArgs()->once();
+ swap(Nginx::class, $nginx);
+
+ $this->tester->run(['command' => 'secure', 'domain' => $domain]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString(
+ $expectedMessage,
+ $output->fetch()
+ );
+ }
+
+ public function unsecureNginxDomainProvider(): array
+ {
+ return [
+ [
+ [
+ 'domain' => 'test-domain',
+ ],
+ 'The [test-domain.test] site will now serve traffic over HTTP',
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider unsecureNginxDomainProvider
+ */
+ public function itWillUnsecureNginxDomain(array $overrides, string $expectedMessage): void
+ {
+ Writer::fake();
+
+ $domain = array_key_exists('domain', $overrides) ? $overrides['domain'] : 'test-domain';
+
+ $siteSecure = Mockery::mock(SiteSecure::class);
+ $siteSecure->shouldReceive('unsecure')->with($domain . '.test', true)->once();
+ swap(SiteSecure::class, $siteSecure);
+
+ $nginx = Mockery::mock(Nginx::class);
+ $nginx->shouldReceive('restart')->withNoArgs()->once();
+ swap(Nginx::class, $nginx);
+
+ $this->tester->run(['command' => 'unsecure', 'domain' => $domain]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString(
+ $expectedMessage,
+ $output->fetch()
+ );
+ }
+
+ public function securedNginxDomainProvider(): array
+ {
+ return [
+ [
+ [
+ 'domain' => 'domain-1',
+ ],
+ 'domain-1.test is secured',
+ ],
+ [
+ [
+ 'domain' => null,
+ ],
+ 'valet-linux-plus.test is secured',
+ ],
+ [
+ [
+ 'domain' => 'unsecure-domain.test',
+ ],
+ 'unsecure-domain.test is not secured',
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider securedNginxDomainProvider
+ */
+ public function itWillListSecuredNginxDomains(array $overrides, string $expectedMessage): void
+ {
+ Writer::fake();
+
+ $domain = array_key_exists('domain', $overrides) ? $overrides['domain'] : 'test-domain';
+
+ $siteSecured = Mockery::mock(SiteSecure::class);
+ $siteSecured->shouldReceive('secured')->withNoArgs()->once()->andReturn(collect([
+ 'domain-1.test',
+ 'domain-2.test',
+ 'valet-linux-plus.test',
+ ]));
+ swap(SiteSecure::class, $siteSecured);
+
+ $this->tester->run(['command' => 'secured', 'site' => $domain]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString(
+ $expectedMessage,
+ $output->fetch()
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillChangePhpVersion(): void
+ {
+ Writer::fake();
+
+ $phpFpm = Mockery::mock(PhpFpm::class);
+ $phpFpm->shouldReceive('normalizePhpVersion')->with('8.2')->andReturn('8.2');
+ $phpFpm->shouldReceive('validateVersion')->with('8.2')->andReturnTrue();
+ $phpFpm->shouldReceive('switchVersion')->with('8.2', true, false)->once();
+ swap(PhpFpm::class, $phpFpm);
+
+ $this->tester->run(['command' => 'use', 'preferredVersion' => '8.2', '--update-cli' => true]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString(
+ 'PHP version successfully changed to [8.2]',
+ $output->fetch()
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillHandleInvalidPhpVersion(): void
+ {
+ Writer::fake();
+
+ $phpFpm = Mockery::mock(PhpFpm::class);
+ $phpFpm->shouldReceive('normalizePhpVersion')->with('7.2')->andReturn('7.2');
+ $phpFpm->shouldReceive('validateVersion')->with('7.2')->andReturnFalse();
+ $phpFpm->shouldReceive('switchVersion')->never();
+ swap(PhpFpm::class, $phpFpm);
+
+ $this->tester->run(['command' => 'use', 'preferredVersion' => '7.2', '--update-cli' => true]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ 'Invalid version [7.2] used. Supported versions are: 8.2, 8.3',
+ $content
+ );
+ $this->assertStringContainsString(
+ \sprintf(
+ 'You can still use any version from [%s] list using `valet isolate` command',
+ '7.0, 7.1, 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3'
+ ),
+ $content
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillListDatabases(): void
+ {
+ Writer::fake();
+
+ $mysql = Mockery::mock(Mysql::class);
+ $mysql->shouldReceive('getDatabases')->withNoArgs()->andReturn([['database1'], ['database2']]);
+ swap(Mysql::class, $mysql);
+
+ $this->tester->run(['command' => 'db:list']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ 'database1',
+ $content
+ );
+ $this->assertStringContainsString(
+ 'database2',
+ $content
+ );
+ }
+
+ public function databaseNamesProvider(): array
+ {
+ return [
+ [
+ 'database1',
+ 'database1',
+ ],
+ [
+ null,
+ basename((string)getcwd())
+ ]
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider databaseNamesProvider
+ */
+ public function itWillCreateDatabase(?string $databaseName, string $expectedDatabaseName): void
+ {
+ Writer::fake();
+
+ $mysql = Mockery::mock(Mysql::class);
+ $mysql->shouldReceive('createDatabase')->with($expectedDatabaseName)->andReturnTrue();
+ swap(Mysql::class, $mysql);
+
+ $this->tester->run(['command' => 'db:create', 'databaseName' => $databaseName]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ \sprintf('Database [%s] created successfully', $expectedDatabaseName),
+ $content
+ );
+ }
+
+ /**
+ * @test
+ * @dataProvider databaseNamesProvider
+ */
+ public function itWillDropDatabase(?string $databaseName, string $expectedDatabaseName): void
+ {
+ Writer::fake();
+
+ $mysql = Mockery::mock(Mysql::class);
+ $mysql->shouldReceive('dropDatabase')->with($expectedDatabaseName)->andReturnTrue();
+ swap(Mysql::class, $mysql);
+
+ $this->tester->run(['command' => 'db:drop', 'databaseName' => $databaseName, '-y' => true]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ \sprintf('Database [%s] dropped successfully', $expectedDatabaseName),
+ $content
+ );
+ }
+
+ /**
+ * @test
+ * @dataProvider databaseNamesProvider
+ */
+ public function itWillResetDatabase(?string $databaseName, string $expectedDatabaseName): void
+ {
+ Writer::fake();
+
+ $mysql = Mockery::mock(Mysql::class);
+ $mysql->shouldReceive('dropDatabase')->with($expectedDatabaseName)->andReturnTrue();
+ $mysql->shouldReceive('createDatabase')->with($expectedDatabaseName)->andReturnTrue();
+ swap(Mysql::class, $mysql);
+
+ $this->tester->run(['command' => 'db:reset', 'databaseName' => $databaseName, '-y' => true]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ \sprintf('Database [%s] reset successfully', $expectedDatabaseName),
+ $content
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillImportDatabase(): void
+ {
+ Writer::fake();
+
+ $databaseName = 'database1';
+ $dumpFilePath = '/path/to/sql-file';
+ $mysql = Mockery::mock(Mysql::class);
+ $mysql->shouldReceive('importDatabase')->with($dumpFilePath, $databaseName);
+ swap(Mysql::class, $mysql);
+
+ $fileSystem = Mockery::mock(Filesystem::class);
+ $fileSystem->shouldReceive('exists')->with($dumpFilePath)->andReturnTrue();
+ swap(Filesystem::class, $fileSystem);
+
+ $this->tester->run(['command' => 'db:import', 'databaseName' => $databaseName, 'dumpFile' => $dumpFilePath]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ 'Importing database...',
+ $content
+ );
+ $this->assertStringContainsString(
+ \sprintf('Database [%s] imported successfully', $databaseName),
+ $content
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillExportDatabase(): void
+ {
+ Writer::fake();
+
+ $databaseName = 'database1';
+ $mysql = Mockery::mock(Mysql::class);
+ $mysql->shouldReceive('exportDatabase')->with($databaseName, true)->andReturn([
+ 'database' => $databaseName,
+ 'filename' => 'database1.sql',
+ ]);
+ swap(Mysql::class, $mysql);
+
+ $this->tester->run(['command' => 'db:export', 'databaseName' => $databaseName, '--sql' => true]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ \sprintf('Database [%s] exported into file database1.sql', $databaseName),
+ $content
+ );
+ }
+
+ public function isolateDirectoryDataProvider(): array
+ {
+ return [
+ [
+ '7.4',
+ 'info.test',
+ 'info.test',
+ true,
+ ],
+ [
+ '7.4',
+ null,
+ basename((string)getcwd()),
+ true,
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider isolateDirectoryDataProvider
+ */
+ public function itWillIsolatePhpVersion(
+ string $phpVersion,
+ ?string $siteName,
+ string $expectedSiteName,
+ bool $isSecure
+ ): void {
+ Writer::fake();
+
+ $siteIsolate = Mockery::mock(SiteIsolate::class);
+ $siteIsolate->shouldReceive('isolateDirectory')
+ ->with($expectedSiteName, $phpVersion, $isSecure)
+ ->once()
+ ->andReturnTrue();
+ swap(SiteIsolate::class, $siteIsolate);
+
+ $arguments = [
+ 'phpVersion' => $phpVersion,
+ '--secure' => $isSecure
+ ];
+ if ($siteName) {
+ $arguments['--site'] = $siteName;
+ }
+
+ $this->tester->run([
+ 'command' => 'isolate',
+ ...$arguments
+ ]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ sprintf('The site [%s] is now using %s.', $expectedSiteName, $phpVersion),
+ $content
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillFailWhenRequireArgumentIsNotAvailable(): void
+ {
+ Writer::fake();
+
+ $siteIsolate = Mockery::mock(SiteIsolate::class);
+ $siteIsolate->shouldReceive('isolateDirectory')->never();
+ swap(SiteIsolate::class, $siteIsolate);
+
+ $this->tester->run(['command' => 'isolate']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ sprintf('Please select version to isolate'),
+ $content
+ );
+ }
+
+ public function unIsolateDirectoryDataProvider(): array
+ {
+ return [
+ [
+ 'info.test',
+ 'info.test',
+ ],
+ [
+ null,
+ basename((string)getcwd()),
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider unIsolateDirectoryDataProvider
+ */
+ public function itWillUnIsolateDirectory(?string $domainName, string $expectedDomainName): void
+ {
+ Writer::fake();
+
+ $siteIsolate = Mockery::mock(SiteIsolate::class);
+ $siteIsolate->shouldReceive('unIsolateDirectory')->with($expectedDomainName)->once()->andReturnTrue();
+ swap(SiteIsolate::class, $siteIsolate);
+
+ $arguments = [];
+ if ($domainName) {
+ $arguments['--site'] = $domainName;
+ }
+ $this->tester->run(['command' => 'unisolate', ...$arguments]);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString(
+ sprintf('The site [%s] is now using the default PHP version.', $expectedDomainName),
+ $content
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillListIsolatedDirectories(): void
+ {
+ Writer::fake();
+
+ $siteIsolate = Mockery::mock(SiteIsolate::class);
+ $siteIsolate->shouldReceive('isolatedDirectories')
+ ->withNoArgs()
+ ->andReturn(
+ collect([
+ [
+ 'url' => 'fpm-site.test',
+ 'secured' => '✓',
+ 'version' => '7.2',
+ ],
+ [
+ 'url' => 'second.test',
+ 'secured' => '✕',
+ 'version' => '8.1'
+ ]
+ ])
+ )->once();
+ swap(SiteIsolate::class, $siteIsolate);
+
+ $this->tester->run(['command' => 'isolated']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString('fpm-site.test | ✓ | 7.2', $content);
+ $this->assertStringContainsString('second.test | ✕ | 8.1', $content);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillStoreNgrokAuthToken(): void
+ {
+ Writer::fake();
+
+ $ngrok = Mockery::mock(Ngrok::class);
+ $ngrok->shouldReceive('setAuthToken')
+ ->with('auth-token')->once();
+ swap(Ngrok::class, $ngrok);
+
+ $this->tester->run(['command' => 'ngrok-auth', 'authtoken' => 'auth-token']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString('Ngrok authentication token set', $content);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillThrowErrorWhenTokenNotProvided(): void
+ {
+ Writer::fake();
+
+ $ngrok = Mockery::mock(Ngrok::class);
+ $ngrok->shouldReceive('setAuthToken')->never();
+ swap(Ngrok::class, $ngrok);
+
+ $this->tester->run(['command' => 'ngrok-auth']);
+
+ $this->tester->assertCommandIsSuccessful();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $content = $output->fetch();
+ $this->assertStringContainsString('Please provide ngrok auth token', $content);
+ }
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
new file mode 100644
index 0000000..f1846ef
--- /dev/null
+++ b/tests/TestCase.php
@@ -0,0 +1,51 @@
+prepTestConfig();
+ }
+
+ public function tearDown(): void
+ {
+ \Mockery::close();
+ }
+
+ /**
+ * Prepare a test to run using the full application.
+ */
+ public function prepTestConfig(): void
+ {
+ require_once __DIR__ . '/../cli/includes/helpers.php';
+ Container::setInstance(new Container()); // Reset app container from previous tests
+ $files = new Filesystem();
+ if ($files->isDir(VALET_HOME_PATH)) {
+ $files->remove(VALET_HOME_PATH);
+ }
+
+ Configuration::install();
+
+ // Keep this file empty, as it's tailed in a test
+ $files->touch(VALET_HOME_PATH . '/Log/nginx-error.log');
+
+ require __DIR__ . '/../cli/app.php';
+
+ /** @var Application $app */
+ $this->app = $app;
+ $this->app->setAutoExit(false);
+ $this->tester = new ApplicationTester($app);
+ }
+}
diff --git a/tests/Unit/ConfigurationTest.php b/tests/Unit/ConfigurationTest.php
new file mode 100644
index 0000000..3a09e84
--- /dev/null
+++ b/tests/Unit/ConfigurationTest.php
@@ -0,0 +1,426 @@
+filesystem = \Mockery::mock(Filesystem::class);
+ $this->configuration = new Configuration($this->filesystem);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillInstallConfigurationSuccessfully(): void
+ {
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH, user());
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Drivers')
+ ->andReturn(false);
+ $this->filesystem
+ ->shouldReceive('mkdirAsUser')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Drivers')
+ ->andReturn(false);
+ $this->filesystem
+ ->shouldReceive('get')
+ ->with(VALET_ROOT_PATH.'/cli/stubs/SampleValetDriver.php')
+ ->once()
+ ->andReturn('Sample Valet Driver Content');
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Drivers/SampleValetDriver.php', 'Sample Valet Driver Content');
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Sites', user());
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Extensions', user());
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Log', user());
+ $this->filesystem
+ ->shouldReceive('touch')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Log/nginx-error.log');
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Certificates', user());
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH.'/config.json')
+ ->andReturn(false);
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->once()
+ ->with(
+ VALET_HOME_PATH.'/config.json',
+ json_encode(
+ $this->defaultConfig(),
+ JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
+ ).PHP_EOL
+ );
+ $this->filesystem
+ ->shouldReceive('chown')
+ ->once()
+ ->with(VALET_HOME_PATH.'/config.json', user())
+ ->andReturn(false);
+
+ $this->configuration->install();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillNotOverrideWhenAlreadyInstalled(): void
+ {
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH, user());
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Drivers')
+ ->andReturn(true);
+ $this->filesystem
+ ->shouldNotReceive('mkdirAsUser');
+ $this->filesystem
+ ->shouldNotReceive('get')
+ ->with(VALET_ROOT_PATH.'/cli/stubs/SampleValetDriver.php');
+ $this->filesystem
+ ->shouldNotReceive('putAsUser')
+ ->with(VALET_HOME_PATH.'/Drivers/SampleValetDriver.php', 'Sample Valet Driver Content');
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Sites', user());
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Extensions', user());
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Log', user());
+ $this->filesystem
+ ->shouldReceive('touch')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Log/nginx-error.log');
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH.'/Certificates', user());
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH.'/config.json')
+ ->andReturn(true);
+ $this->filesystem
+ ->shouldNotReceive('putAsUser')
+ ->with(
+ VALET_HOME_PATH.'/config.json',
+ json_encode(
+ $this->defaultConfig(),
+ JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
+ ).PHP_EOL
+ );
+ $this->filesystem
+ ->shouldReceive('chown')
+ ->once()
+ ->with(VALET_HOME_PATH.'/config.json', user())
+ ->andReturn(false);
+
+ $this->configuration->install();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUninstallSuccessfully(): void
+ {
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->once()
+ ->with(VALET_HOME_PATH)
+ ->andReturn(true);
+ $this->filesystem
+ ->shouldReceive('remove')
+ ->once()
+ ->with(VALET_HOME_PATH);
+
+ $this->configuration->uninstall();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillNotUninstallWhenDirectoryNotExist(): void
+ {
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->once()
+ ->with(VALET_HOME_PATH)
+ ->andReturn(false);
+ $this->filesystem
+ ->shouldNotReceive('remove')
+ ->with(VALET_HOME_PATH);
+
+ $this->configuration->uninstall();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillAddPathToConfig(): void
+ {
+ $defaultConfig = $this->defaultConfig();
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH.'/config.json')
+ ->andReturn(json_encode($defaultConfig));
+
+ $defaultConfig['paths'] = ['new_path'];
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with(
+ VALET_HOME_PATH.'/config.json',
+ json_encode(
+ $defaultConfig,
+ JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
+ ).PHP_EOL
+ );
+
+ $this->configuration->addPath('new_path');
+ }
+
+ /**
+ * @test
+ */
+ public function itWillRemovePathFromConfig(): void
+ {
+ $defaultConfig = $this->defaultConfig();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH.'/config.json')
+ ->andReturn(json_encode([...$defaultConfig, 'paths' => ['new_path']]));
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with(
+ VALET_HOME_PATH.'/config.json',
+ json_encode(
+ $defaultConfig,
+ JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
+ ).PHP_EOL
+ );
+
+ $this->configuration->removePath('new_path');
+ }
+
+ /**
+ * @test
+ */
+ public function itWillRemoveBrokenPathsFromConfig(): void
+ {
+ $defaultConfig = $this->defaultConfig();
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH.'/config.json')
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH.'/config.json')
+ ->andReturn(json_encode([...$defaultConfig, 'paths' => ['new_path']]));
+
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->once()
+ ->with('new_path')
+ ->andReturnFalse();
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with(
+ VALET_HOME_PATH.'/config.json',
+ json_encode(
+ $defaultConfig,
+ JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
+ ).PHP_EOL
+ );
+
+ $this->configuration->prune();
+ }
+
+ public function configDataProvider(): array
+ {
+ return [
+ [
+ 'domain',
+ null,
+ 'test',
+ ],
+ [
+ 'paths',
+ null,
+ [],
+ ],
+ [
+ 'port',
+ null,
+ '80',
+ ],
+ [
+ 'mysql',
+ [],
+ [],
+ ]
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider configDataProvider
+ */
+ public function itWillGetValueFromConfig(string $configKey, mixed $defaultValue, mixed $expectedValue): void
+ {
+ $defaultConfig = $this->defaultConfig();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH.'/config.json')
+ ->andReturn(json_encode($defaultConfig));
+
+ $value = $this->configuration->get($configKey, $defaultValue);
+
+ $this->assertSame($expectedValue, $value);
+ }
+
+ public function updateConfigDataProvider(): array
+ {
+ return [
+ [
+ 'domain',
+ 'new_domain',
+ ],
+ [
+ 'paths',
+ ['test_path', 'new_path'],
+ ],
+ [
+ 'mysql',
+ ['user' => 'mysql', 'password' => 'password'],
+ ]
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider updateConfigDataProvider
+ */
+ public function itWillSetValueForConfig(string $configKey, mixed $updateValue): void
+ {
+ $defaultConfig = $this->defaultConfig();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH.'/config.json')
+ ->andReturn(json_encode($defaultConfig));
+
+ $updatedConfig = [...$defaultConfig, ...[$configKey => $updateValue]];
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->once()
+ ->with(
+ VALET_HOME_PATH.'/config.json',
+ json_encode(
+ $updatedConfig,
+ JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
+ ).PHP_EOL
+ );
+
+ $value = $this->configuration->set($configKey, $updateValue);
+
+ $this->assertSame($updatedConfig, $value);
+ }
+
+ public function domainDataProvider(): array
+ {
+ return [
+ [
+ 'test',
+ 'test.test',
+ ],
+ [
+ 'site.test',
+ 'site.test',
+ ],
+ [
+ 'site.localhost',
+ 'site.localhost.test',
+ ]
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider domainDataProvider
+ */
+ public function itWillParseDomain(string $siteName, string $expectedDomain): void
+ {
+ $defaultConfig = $this->defaultConfig();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH.'/config.json')
+ ->andReturn(json_encode($defaultConfig));
+
+ $domain = $this->configuration->parseDomain($siteName);
+
+ $this->assertSame($expectedDomain, $domain);
+ }
+
+ private function defaultConfig(): array
+ {
+ return [
+ 'domain' => 'test',
+ 'paths' => [],
+ 'port' => '80',
+ ];
+ }
+}
diff --git a/tests/Unit/DevToolsTest.php b/tests/Unit/DevToolsTest.php
new file mode 100644
index 0000000..36fda8a
--- /dev/null
+++ b/tests/Unit/DevToolsTest.php
@@ -0,0 +1,204 @@
+packageManager = Mockery::mock(PackageManager::class);
+ $this->serviceManager = Mockery::mock(ServiceManager::class);
+ $this->commandLine = Mockery::mock(CommandLine::class);
+ $this->filesystem = Mockery::mock(Filesystem::class);
+
+ $this->devTools = new DevTools(
+ $this->packageManager,
+ $this->serviceManager,
+ $this->commandLine,
+ $this->filesystem
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillGetBinForGivenServiceUsingWhichCommand(): void
+ {
+ $service = 'service_name';
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function ($path, $callback) use ($service) {
+ $this->assertSame('which ' . $service, $path);
+ return '/path/to/bin/service_name';
+ });
+
+ $binPath = $this->devTools->getBin($service);
+
+ $this->assertSame('/path/to/bin/service_name', $binPath);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillGetBinForGivenServiceUsingLocateCommand(): void
+ {
+ $service = 'service_name';
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function ($path, $callback) use ($service) {
+ $this->assertSame('which ' . $service, $path);
+ $callback(1, '');
+ });
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function ($path, $callback) use ($service) {
+ $this->assertSame('locate --regex bin/' . $service. '$', $path);
+ return "/path/to/bin/service_name\n/second-path/bin/service_name\n/third-path/bin/service_name\n";
+ });
+
+ $binPath = $this->devTools->getBin($service);
+
+ $this->assertSame('/path/to/bin/service_name', $binPath);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillGetBinForGivenServiceAndExcludeGivenServiceByWhichCommand(): void
+ {
+ $service = 'service_name';
+ $excludedServices = ['/path/to/bin/service_name'];
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function ($path, $callback) use ($service) {
+ $this->assertSame('which ' . $service, $path);
+ return '/path/to/bin/service_name';
+ });
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function ($path, $callback) use ($service) {
+ $this->assertSame('locate --regex bin/' . $service. '$', $path);
+ return "/path/to/bin/service_name\n/second-path/bin/service_name\n/third-path/bin/service_name\n";
+ });
+
+ $binPath = $this->devTools->getBin($service, $excludedServices);
+
+ $this->assertSame('/second-path/bin/service_name', $binPath);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillGetBinForGivenServiceAndExcludeGivenServiceByLocateCommand(): void
+ {
+ $service = 'service_name';
+ $excludedServices = ['/path/to/bin/service_name'];
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function ($path, $callback) use ($service) {
+ $this->assertSame('which ' . $service, $path);
+ $callback(1, '');
+ });
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function ($path, $callback) use ($service) {
+ $this->assertSame('locate --regex bin/' . $service. '$', $path);
+ return "/path/to/bin/service_name\n/second-path/bin/service_name\n/third-path/bin/service_name\n";
+ });
+
+ $binPath = $this->devTools->getBin($service, $excludedServices);
+
+ $this->assertSame('/second-path/bin/service_name', $binPath);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillRunServiceWhenAvailable(): void
+ {
+ $service = 'service_name';
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function ($path, $callback) use ($service) {
+ $this->assertSame('which ' . $service, $path);
+ return '/path/to/bin/service_name';
+ });
+
+ $this->commandLine
+ ->shouldReceive('quietly')
+ ->once()
+ ->with('/path/to/bin/service_name /path/to/folder');
+
+ $this->devTools->run('/path/to/folder', $service);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillShowOutputWhenServiceNotAvailableToRun(): void
+ {
+ Writer::fake();
+ $service = 'service_name';
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function ($path, $callback) use ($service) {
+ $this->assertSame('which ' . $service, $path);
+ $callback(1, '');
+ });
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function ($path, $callback) use ($service) {
+ $this->assertSame('locate --regex bin/' . $service. '$', $path);
+ return "";
+ });
+
+ $this->commandLine
+ ->shouldNotReceive('quietly');
+
+ $this->devTools->run('/path/to/folder', $service);
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString('service_name not available', $output->fetch());
+ }
+}
diff --git a/tests/Unit/DnsMasqTest.php b/tests/Unit/DnsMasqTest.php
new file mode 100644
index 0000000..9a39d76
--- /dev/null
+++ b/tests/Unit/DnsMasqTest.php
@@ -0,0 +1,328 @@
+packageManager = Mockery::mock(PackageManager::class);
+ $this->serviceManager = Mockery::mock(ServiceManager::class);
+ $this->filesystem = Mockery::mock(Filesystem::class);
+ $this->commandLine = Mockery::mock(CommandLine::class);
+
+ $this->dnsMasq = new DnsMasq(
+ $this->packageManager,
+ $this->serviceManager,
+ $this->filesystem,
+ $this->commandLine
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillInstallSuccessfully(): void
+ {
+ $this->packageManager
+ ->shouldReceive('ensureInstalled')
+ ->once()
+ ->with('dnsmasq');
+
+ $this->serviceManager
+ ->shouldReceive('enable')
+ ->once()
+ ->with('dnsmasq');
+
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with('/etc/NetworkManager/conf.d');
+
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with('/etc/dnsmasq.d');
+
+ $this->filesystem
+ ->shouldReceive('uncommentLine')
+ ->once()
+ ->with('IGNORE_RESOLVCONF', '/etc/default/dnsmasq');
+
+ $this->filesystem
+ ->shouldReceive('isLink')
+ ->once()
+ ->with('/etc/resolv.conf')
+ ->andReturnFalse();
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function (string $command): string {
+ $this->assertSame('chattr -i /etc/resolv.conf', $command);
+ return '';
+ });
+
+ $this->filesystem
+ ->shouldReceive('remove')
+ ->once()
+ ->with('/opt/valet-linux');
+
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with('/opt/valet-linux');
+
+ $this->serviceManager
+ ->shouldReceive('removeValetDns')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with('/etc/rc.local')
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('restore')
+ ->once()
+ ->with('/etc/rc.local');
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->once()
+ ->with('/etc/dnsmasq.d/network-manager');
+
+ $this->filesystem
+ ->shouldReceive('backup')
+ ->once()
+ ->with('/etc/dnsmasq.conf');
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_ROOT_PATH.'/cli/stubs/dnsmasq.conf')
+ ->andReturn('dnsmasq.conf content');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->once()
+ ->with('/etc/dnsmasq.conf', 'dnsmasq.conf content');
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_ROOT_PATH.'/cli/stubs/dnsmasq_options')
+ ->andReturn('dnsmasq_options content');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->once()
+ ->with('/etc/dnsmasq.d/options', 'dnsmasq_options content');
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_ROOT_PATH.'/cli/stubs/networkmanager.conf')
+ ->andReturn('networkmanager.conf content');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->once()
+ ->with('/etc/NetworkManager/conf.d/valet.conf', 'networkmanager.conf content');
+
+ $this->serviceManager
+ ->shouldReceive('disabled')
+ ->with('systemd-resolved')
+ ->andReturnFalse();
+
+ $this->serviceManager
+ ->shouldReceive('disable')
+ ->with('systemd-resolved');
+
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->with('systemd-resolved');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with(
+ '/etc/dnsmasq.d/valet',
+ 'address=/.test/127.0.0.1'.PHP_EOL.'server=1.1.1.1'.PHP_EOL.'server=8.8.8.8'.PHP_EOL
+ );
+
+ $this->serviceManager
+ ->shouldReceive('restart')
+ ->with('dnsmasq');
+
+ $this->dnsMasq->install('test');
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUninstallSuccessfully(): void
+ {
+ Writer::fake();
+
+ $this->serviceManager
+ ->shouldReceive('removeValetDns')
+ ->once();
+
+ $this->commandLine
+ ->shouldReceive('passthru')
+ ->with('rm -rf /opt/valet-linux');
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->once()
+ ->with('/etc/dnsmasq.d/valet');
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->once()
+ ->with('/etc/dnsmasq.d/options');
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->once()
+ ->with('/etc/NetworkManager/conf.d/valet.conf');
+
+ $this->filesystem
+ ->shouldReceive('restore')
+ ->once()
+ ->with('/etc/systemd/resolved.conf');
+
+ $this->filesystem
+ ->shouldReceive('isLink')
+ ->once()
+ ->with('/etc/resolv.conf')
+ ->andReturnFalse();
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function (string $command): string {
+ $this->assertSame('chattr -i /etc/resolv.conf', $command);
+ return '';
+ });
+
+ $this->filesystem
+ ->shouldReceive('restore')
+ ->once()
+ ->with('/etc/rc.local');
+
+ $this->commandLine
+ ->shouldReceive('passthru')
+ ->once()
+ ->with('rm -f /etc/resolv.conf');
+
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->once()
+ ->with('systemd-resolved');
+
+ $this->serviceManager
+ ->shouldReceive('start')
+ ->once()
+ ->with('systemd-resolved');
+
+ $this->filesystem
+ ->shouldReceive('symlink')
+ ->once()
+ ->with('/run/systemd/resolve/resolv.conf', '/etc/resolv.conf');
+
+ $this->filesystem
+ ->shouldReceive('restore')
+ ->once()
+ ->with('/etc/dnsmasq.conf');
+
+ $this->filesystem
+ ->shouldReceive('commentLine')
+ ->once()
+ ->with('IGNORE_RESOLVCONF', '/etc/default/dnsmasq');
+
+ $this->packageManager
+ ->shouldReceive('restartNetworkManager')
+ ->once()
+ ->withNoArgs();
+
+ $this->serviceManager
+ ->shouldReceive('restart')
+ ->once()
+ ->with('dnsmasq');
+
+ $this->dnsMasq->uninstall();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+
+ $this->assertStringContainsString('Valet DNS changes have been rolled back', $output->fetch());
+ }
+
+ /**
+ * @test
+ */
+ public function itWillStopServiceSuccessfully(): void
+ {
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->once()
+ ->with('dnsmasq');
+
+ $this->dnsMasq->stop();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillRestartServiceSuccessfully(): void
+ {
+ $this->serviceManager
+ ->shouldReceive('restart')
+ ->once()
+ ->with('dnsmasq');
+
+ $this->dnsMasq->restart();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUpdateDomainSuccessfully(): void
+ {
+ $domain = 'local';
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with(
+ '/etc/dnsmasq.d/valet',
+ 'address=/.'.$domain.'/127.0.0.1'.PHP_EOL.'server=1.1.1.1'.PHP_EOL.'server=8.8.8.8'.PHP_EOL
+ );
+
+ $this->serviceManager
+ ->shouldReceive('restart')
+ ->once()
+ ->with('dnsmasq');
+
+ $this->dnsMasq->updateDomain($domain);
+ }
+}
diff --git a/tests/Unit/MailpitTest.php b/tests/Unit/MailpitTest.php
new file mode 100644
index 0000000..dc3855a
--- /dev/null
+++ b/tests/Unit/MailpitTest.php
@@ -0,0 +1,211 @@
+packageManager = Mockery::mock(PackageManager::class);
+ $this->serviceManager = Mockery::mock(ServiceManager::class);
+ $this->filesystem = Mockery::mock(Filesystem::class);
+ $this->commandLine = Mockery::mock(CommandLine::class);
+
+ $this->config = Mockery::mock(Configuration::class);
+ swap(Configuration::class, $this->config);
+
+ $this->siteProxy = Mockery::mock(SiteProxy::class);
+ swap(SiteProxy::class, $this->siteProxy);
+
+ $this->siteSecure = Mockery::mock(SiteSecure::class);
+ swap(SiteSecure::class, $this->siteSecure);
+
+ $this->mailpit = new Mailpit(
+ $this->packageManager,
+ $this->serviceManager,
+ $this->commandLine,
+ $this->filesystem
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillInstallSuccessfully(): void
+ {
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturnUsing(function ($command) {
+ $this->assertSame('which mailpit', $command);
+ return false;
+ });
+
+ $this->commandLine
+ ->shouldReceive('runAsUser')
+ ->once()
+ ->with('curl -sL https://raw.githubusercontent.com/axllent/mailpit/develop/install.sh | bash');
+
+ $this->serviceManager
+ ->shouldReceive('isSystemd')
+ ->once()
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_ROOT_PATH . '/cli/stubs/init/mailpit')
+ ->andReturn('service file content');
+
+ $this->filesystem
+ ->shouldReceive('put')
+ ->once()
+ ->with('/etc/systemd/system/mailpit.service', 'service file content');
+
+ $this->serviceManager
+ ->shouldReceive('enable')
+ ->once()
+ ->with('mailpit');
+
+ $this->config
+ ->shouldReceive('get')
+ ->twice()
+ ->with('domain')
+ ->andReturn('test');
+
+ $this->siteProxy
+ ->shouldReceive('proxyCreate')
+ ->once()
+ ->with('mails.test', 'http://localhost:8025', true);
+
+ $this->serviceManager
+ ->shouldReceive('start')
+ ->once()
+ ->with('mailpit');
+
+ $this->serviceManager
+ ->shouldReceive('disabled')
+ ->once()
+ ->with('mailhog')
+ ->andReturnFalse();
+
+ $this->serviceManager
+ ->shouldReceive('disable')
+ ->once()
+ ->with('mailhog');
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with('/opt/valet-linux/mailhog')
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('remove')
+ ->once()
+ ->with('/opt/valet-linux/mailhog');
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . "/Nginx/mailhog.test")
+ ->andReturnTrue();
+
+ $this->siteSecure
+ ->shouldReceive('unsecure')
+ ->once()
+ ->with('mailhog.test');
+
+ $this->mailpit->install();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillStartServiceSuccessfully(): void
+ {
+ $this->serviceManager
+ ->shouldReceive('start')
+ ->once()
+ ->with('mailpit');
+
+ $this->mailpit->start();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillRestartServiceSuccessfully(): void
+ {
+ $this->serviceManager
+ ->shouldReceive('restart')
+ ->once()
+ ->with('mailpit');
+
+ $this->mailpit->restart();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillStopServiceSuccessfully(): void
+ {
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->once()
+ ->with('mailpit');
+
+ $this->mailpit->stop();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillPrintStatus(): void
+ {
+ $this->serviceManager
+ ->shouldReceive('printStatus')
+ ->once()
+ ->with('mailpit');
+
+ $this->mailpit->status();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUninstallServiceSuccessfully(): void
+ {
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->once()
+ ->with('mailpit');
+
+ $this->mailpit->uninstall();
+ }
+}
diff --git a/tests/Unit/MockResponse/github_response.json b/tests/Unit/MockResponse/github_response.json
new file mode 100644
index 0000000..c73bb63
--- /dev/null
+++ b/tests/Unit/MockResponse/github_response.json
@@ -0,0 +1,41 @@
+{
+ "url": "https://api.github.com/repos/genesisweb/valet-linux-plus/releases/150098134",
+ "assets_url": "https://api.github.com/repos/genesisweb/valet-linux-plus/releases/150098134/assets",
+ "upload_url": "https://uploads.github.com/repos/genesisweb/valet-linux-plus/releases/150098134/assets{?name,label}",
+ "html_url": "https://github.com/genesisweb/valet-linux-plus/releases/tag/1.6.9",
+ "id": 150098134,
+ "author": {
+ "login": "uttamrabadiya",
+ "id": 48178542,
+ "node_id": "MDQ6VXNlcjQ4MTc4NTQy",
+ "avatar_url": "https://avatars.githubusercontent.com/u/48178542?v=4",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/uttamrabadiya",
+ "html_url": "https://github.com/uttamrabadiya",
+ "followers_url": "https://api.github.com/users/uttamrabadiya/followers",
+ "following_url": "https://api.github.com/users/uttamrabadiya/following{/other_user}",
+ "gists_url": "https://api.github.com/users/uttamrabadiya/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/uttamrabadiya/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/uttamrabadiya/subscriptions",
+ "organizations_url": "https://api.github.com/users/uttamrabadiya/orgs",
+ "repos_url": "https://api.github.com/users/uttamrabadiya/repos",
+ "events_url": "https://api.github.com/users/uttamrabadiya/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/uttamrabadiya/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "node_id": "RE_kwDODQn61c4I8lDW",
+ "tag_name": "1.6.9",
+ "target_commitish": "master",
+ "name": "1.6.9",
+ "draft": false,
+ "prerelease": false,
+ "created_at": "2024-04-07T07:07:57Z",
+ "published_at": "2024-04-07T07:10:08Z",
+ "assets": [
+
+ ],
+ "tarball_url": "https://api.github.com/repos/genesisweb/valet-linux-plus/tarball/1.6.9",
+ "zipball_url": "https://api.github.com/repos/genesisweb/valet-linux-plus/zipball/1.6.9",
+ "body": "Fixes:\r\n\r\n- Updated supported version\r\n- Updated ngrok binary"
+}
diff --git a/tests/Unit/MockResponse/ngrok_response.json b/tests/Unit/MockResponse/ngrok_response.json
new file mode 100644
index 0000000..33c10b5
--- /dev/null
+++ b/tests/Unit/MockResponse/ngrok_response.json
@@ -0,0 +1 @@
+{"tunnels":[{"name":"command_line","ID":"204020b4b272cf3ed13630aa5c18772b","uri":"/api/tunnels/command_line","public_url":"https://33e2-2405-201-2024-a899-720-a588-f13d-82fe.ngrok-free.app","proto":"http","config":{"addr":"http://info.localhost:80","inspect":true},"metrics":{"conns":{"count":0,"gauge":0,"rate1":0,"rate5":0,"rate15":0,"p50":0,"p90":0,"p95":0,"p99":0},"http":{"count":0,"rate1":0,"rate5":0,"rate15":0,"p50":0,"p90":0,"p95":0,"p99":0}}}],"uri":"/api/tunnels"}
diff --git a/tests/Unit/MysqlTest.php b/tests/Unit/MysqlTest.php
new file mode 100644
index 0000000..e82992a
--- /dev/null
+++ b/tests/Unit/MysqlTest.php
@@ -0,0 +1,378 @@
+packageManager = Mockery::mock(PackageManager::class);
+ $this->serviceManager = Mockery::mock(ServiceManager::class);
+ $this->commandLine = Mockery::mock(CommandLine::class);
+ $this->filesystem = Mockery::mock(Filesystem::class);
+ $this->config = Mockery::mock(Configuration::class);
+
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with('mysql')
+ ->once()
+ ->andReturn('mysql-server');
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with('mysql-server')
+ ->once()
+ ->andReturnFalse();
+
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with('mariadb')
+ ->once()
+ ->andReturn('mariadb-server');
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with('mariadb-server')
+ ->once()
+ ->andReturnFalse();
+
+ $this->mysql = new Mysql(
+ $this->packageManager,
+ $this->serviceManager,
+ $this->commandLine,
+ $this->filesystem,
+ $this->config
+ );
+ }
+
+ public function packageDataProvider(): array
+ {
+ return [
+ 'mysql' => [
+ false,
+ 'mysql',
+ 'mysql-server',
+ ],
+ 'mariadb' => [
+ true,
+ 'mariadb',
+ 'mariadb-server',
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider packageDataProvider
+ */
+ public function itWillInstallSuccessfully(bool $useMariaDB, string $packageName, string $packageServerName): void
+ {
+ Writer::fake();
+ $phpFpm = Mockery::mock(PhpFpm::class);
+ swap(PhpFpm::class, $phpFpm);
+ $phpFpm->shouldReceive('getCurrentVersion')->once()->andReturn('8.2');
+
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with($packageName)
+ ->once()
+ ->andReturn($packageServerName);
+
+ $this->packageManager
+ ->shouldReceive('ensureInstalled')
+ ->with('php8.2-mysql')
+ ->once();
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with($packageServerName)
+ ->once()
+ ->andReturnFalse();
+
+ $this->packageManager
+ ->shouldReceive('installOrFail')
+ ->with($packageServerName)
+ ->once()
+ ->andReturnFalse();
+
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with('mariadb')
+ ->twice()
+ ->andReturn('mariadb-server');
+
+ $this->serviceManager
+ ->shouldReceive('enable')
+ ->with($packageName)
+ ->once()
+ ->andReturnFalse();
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->andReturn('');
+
+ $this->config
+ ->shouldReceive('get')
+ ->with('mysql', [])
+ ->once()
+ ->andReturn([]);
+
+ $this->config
+ ->shouldReceive('set')
+ ->with('mysql', ['user' => 'valet', 'password' => ''])
+ ->once()
+ ->andReturn([]);
+
+ $this->mysql->install($useMariaDB);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillNotOverrideWhenInstalled()
+ {
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with('mysql')
+ ->once()
+ ->andReturn('mysql-server');
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with('mysql-server')
+ ->once()
+ ->andReturnTrue();
+
+ $mysql = new Mysql(
+ $this->packageManager,
+ $this->serviceManager,
+ $this->commandLine,
+ $this->filesystem,
+ $this->config
+ );
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with('mysql-server')
+ ->once()
+ ->andReturnTrue();
+
+ $this->config
+ ->shouldReceive('get')
+ ->with('mysql', [])
+ ->once()
+ ->andReturn(['user' => 'valet', 'password' => 'valet-password']);
+
+ $this->packageManager
+ ->shouldNotReceive('installOrFail')
+ ->with('mysql-server');
+ $this->serviceManager
+ ->shouldNotReceive('enable')
+ ->with('mysql');
+
+ $mysql->install();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillNotOverrideWhenMariaDbInstalled()
+ {
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with('mysql')
+ ->once()
+ ->andReturn('mysql-server');
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with('mysql-server')
+ ->once()
+ ->andReturnFalse();
+
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with('mariadb')
+ ->once()
+ ->andReturn('mariadb-server');
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with('mariadb-server')
+ ->once()
+ ->andReturnTrue();
+
+ $mysql = new Mysql(
+ $this->packageManager,
+ $this->serviceManager,
+ $this->commandLine,
+ $this->filesystem,
+ $this->config
+ );
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with('mariadb-server')
+ ->once()
+ ->andReturnTrue();
+
+ $this->config
+ ->shouldReceive('get')
+ ->with('mysql', [])
+ ->once()
+ ->andReturn(['user' => 'valet', 'password' => 'valet-password']);
+
+ $this->packageManager
+ ->shouldNotReceive('installOrFail')
+ ->with('mariadb-server');
+ $this->serviceManager
+ ->shouldNotReceive('enable')
+ ->with('mariadb');
+
+ $mysql->install(true);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillStopService(): void
+ {
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with('mysql')
+ ->once()
+ ->andReturn('mysql-server');
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with('mysql-server')
+ ->once()
+ ->andReturnTrue();
+
+ $mysql = new Mysql(
+ $this->packageManager,
+ $this->serviceManager,
+ $this->commandLine,
+ $this->filesystem,
+ $this->config
+ );
+
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with('mariadb')
+ ->once()
+ ->andReturn('mariadb-server');
+
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->with('mysql')
+ ->once()
+ ->andReturnTrue();
+
+ $mysql->stop();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillRestartService(): void
+ {
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with('mysql')
+ ->once()
+ ->andReturn('mysql-server');
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with('mysql-server')
+ ->once()
+ ->andReturnTrue();
+
+ $mysql = new Mysql(
+ $this->packageManager,
+ $this->serviceManager,
+ $this->commandLine,
+ $this->filesystem,
+ $this->config
+ );
+
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with('mariadb')
+ ->once()
+ ->andReturn('mariadb-server');
+
+ $this->serviceManager
+ ->shouldReceive('restart')
+ ->with('mysql')
+ ->once()
+ ->andReturnTrue();
+
+ $mysql->restart();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUninstallService(): void
+ {
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with('mysql')
+ ->once()
+ ->andReturn('mysql-server');
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with('mysql-server')
+ ->once()
+ ->andReturnTrue();
+
+ $mysql = new Mysql(
+ $this->packageManager,
+ $this->serviceManager,
+ $this->commandLine,
+ $this->filesystem,
+ $this->config
+ );
+
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->with('mariadb')
+ ->once()
+ ->andReturn('mariadb-server');
+
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->with('mysql')
+ ->once()
+ ->andReturnTrue();
+
+ $mysql->uninstall();
+ }
+}
diff --git a/tests/Unit/NginxTest.php b/tests/Unit/NginxTest.php
new file mode 100644
index 0000000..92a6239
--- /dev/null
+++ b/tests/Unit/NginxTest.php
@@ -0,0 +1,388 @@
+packageManager = Mockery::mock(PackageManager::class);
+ $this->serviceManager = Mockery::mock(ServiceManager::class);
+ $this->filesystem = Mockery::mock(Filesystem::class);
+ $this->commandLine = Mockery::mock(CommandLine::class);
+ $this->config = Mockery::mock(Configuration::class);
+ $this->siteSecure = Mockery::mock(SiteSecure::class);
+
+ $this->nginx = new Nginx(
+ $this->packageManager,
+ $this->serviceManager,
+ $this->commandLine,
+ $this->filesystem,
+ $this->config,
+ $this->siteSecure
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillInstallSuccessfully(): void
+ {
+ $this->packageManager
+ ->shouldReceive('ensureInstalled')
+ ->once()
+ ->with('nginx');
+
+ $this->serviceManager
+ ->shouldReceive('enable')
+ ->once()
+ ->with('nginx');
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with('apache2')
+ ->once()
+ ->andReturnTrue();
+
+ $this->serviceManager
+ ->shouldReceive('disabled')
+ ->once()
+ ->with('apache2')
+ ->andReturnFalse();
+
+ $this->serviceManager
+ ->shouldReceive('disable')
+ ->once()
+ ->with('apache2');
+
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->once()
+ ->with('apache2');
+
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with('/etc/nginx/sites-available');
+
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with('/etc/nginx/sites-enabled');
+
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->once()
+ ->with('nginx');
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->with(VALET_ROOT_PATH . '/cli/stubs/nginx.conf')
+ ->once()
+ ->andReturn('nginx.conf content');
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->with('cat /lib/systemd/system/nginx.service')
+ ->once()
+ ->andReturn('pid /run/nginx.pid');
+
+ $this->filesystem
+ ->shouldReceive('backup')
+ ->with('/etc/nginx/nginx.conf')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with('/etc/nginx/nginx.conf', 'nginx.conf content')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->with(VALET_ROOT_PATH . '/cli/stubs/valet.conf')
+ ->once()
+ ->andReturn('valet.conf content');
+
+ $this->config
+ ->shouldReceive('get')
+ ->with('port')
+ ->once()
+ ->andReturn('80');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with('/etc/nginx/sites-available/valet.conf', 'valet.conf content')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->with('/etc/nginx/sites-enabled/default')
+ ->once()
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->with('/etc/nginx/sites-enabled/default')
+ ->once();
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->with('ln -snf /etc/nginx/sites-available/valet.conf /etc/nginx/sites-enabled/valet.conf')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('backup')
+ ->with('/etc/nginx/fastcgi_params')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->with(VALET_ROOT_PATH . '/cli/stubs/fastcgi_params')
+ ->once()
+ ->andReturn('fastcgi_params content');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with('/etc/nginx/fastcgi_params', 'fastcgi_params content')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->with(VALET_HOME_PATH . '/Nginx')
+ ->once()
+ ->andReturnFalse();
+
+ $this->filesystem
+ ->shouldReceive('mkdirAsUser')
+ ->with(VALET_HOME_PATH . '/Nginx')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with(VALET_HOME_PATH . '/Nginx/.keep', "\n")
+ ->once();
+
+ $this->config
+ ->shouldReceive('get')
+ ->with('domain')
+ ->once()
+ ->andReturn('test');
+
+ $this->siteSecure
+ ->shouldReceive('reSecureForNewDomain')
+ ->with('test', 'test')
+ ->once();
+
+ $this->nginx->install();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUpdatePort(): void
+ {
+ $this->filesystem
+ ->shouldReceive('get')
+ ->with(VALET_ROOT_PATH . '/cli/stubs/valet.conf')
+ ->once()
+ ->andReturn('valet.conf content VALET_PORT');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with('/etc/nginx/sites-available/valet.conf', 'valet.conf content 88')
+ ->once();
+
+ $this->nginx->updatePort('88');
+ }
+
+ /**
+ * @test
+ */
+ public function itWillRestartService(): void
+ {
+ $this->serviceManager
+ ->shouldReceive('restart')
+ ->with('nginx')
+ ->once();
+
+ $this->nginx->restart();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillStopService(): void
+ {
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->with('nginx')
+ ->once();
+
+ $this->nginx->stop();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillPrintStatusOfService(): void
+ {
+ $this->serviceManager
+ ->shouldReceive('printStatus')
+ ->with('nginx')
+ ->once();
+
+ $this->nginx->status();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUninstall(): void
+ {
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->with('nginx')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('restore')
+ ->with('/etc/nginx/nginx.conf')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('restore')
+ ->with('/etc/nginx/fastcgi_params')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->with('/etc/nginx/sites-enabled/valet.conf')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->with('/etc/nginx/sites-available/valet.conf')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->with('/etc/nginx/sites-available/default')
+ ->once()
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('symlink')
+ ->with('/etc/nginx/sites-available/default', '/etc/nginx/sites-enabled/default')
+ ->once();
+
+ $this->nginx->uninstall();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillListConfiguredSites(): void
+ {
+ $this->filesystem
+ ->shouldReceive('scandir')
+ ->with(VALET_HOME_PATH . '/Nginx')
+ ->once()
+ ->andReturn(['site1', 'site2', 'site3', '.hiddenSite']);
+
+ $output = $this->nginx->configuredSites();
+
+ $this->assertSame(['site1', 'site2', 'site3'], $output->all());
+ }
+
+ /**
+ * @test
+ */
+ public function itWillInstallServerByGivenPhpVersion(): void
+ {
+ $phpFpm = Mockery::mock(PhpFpm::class);
+ swap(PhpFpm::class, $phpFpm);
+
+ $phpFpm->shouldReceive('socketFileName')
+ ->with('8.2')
+ ->once()
+ ->andReturn('valet82.sock');
+
+ $this->config
+ ->shouldReceive('get')
+ ->with('port')
+ ->once()
+ ->andReturn('80');
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->with(VALET_ROOT_PATH . '/cli/stubs/valet.conf')
+ ->once()
+ ->andReturn('valet.conf content VALET_FPM_SOCKET_FILE VALET_PORT');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with(
+ '/etc/nginx/sites-available/valet.conf',
+ \sprintf('valet.conf content %s 80', VALET_HOME_PATH . '/valet82.sock')
+ )
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->with('/etc/nginx/sites-enabled/default')
+ ->once()
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->with('/etc/nginx/sites-enabled/default')
+ ->once();
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->with('ln -snf /etc/nginx/sites-available/valet.conf /etc/nginx/sites-enabled/valet.conf')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('backup')
+ ->with('/etc/nginx/fastcgi_params')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->with(VALET_ROOT_PATH . '/cli/stubs/fastcgi_params')
+ ->once()
+ ->andReturn('fastcgi_params content');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with('/etc/nginx/fastcgi_params', 'fastcgi_params content')
+ ->once();
+
+ $this->nginx->installServer('8.2');
+ }
+}
diff --git a/tests/Unit/NgrokTest.php b/tests/Unit/NgrokTest.php
new file mode 100644
index 0000000..dd071fe
--- /dev/null
+++ b/tests/Unit/NgrokTest.php
@@ -0,0 +1,197 @@
+commandLine = Mockery::mock(CommandLine::class);
+ $this->filesystem = Mockery::mock(Filesystem::class);
+
+ $this->ngrok = new Ngrok(
+ $this->commandLine,
+ $this->filesystem
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillInstallSuccessfully(): void
+ {
+ Writer::fake();
+
+ $request = Mockery::mock(Request::class);
+ swap(Request::class, $request);
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(\sprintf('%s/bin/ngrok', VALET_ROOT_PATH))
+ ->andReturnFalse();
+
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(\sprintf('%s/bin', VALET_ROOT_PATH), user());
+
+ $request->shouldReceive('get')
+ ->with('https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz')
+ ->once()
+ ->andReturnSelf();
+
+ $request->shouldReceive('send')
+ ->withNoArgs()
+ ->once()
+ ->andReturn(new Response('ngrok-body-content', 'headers 0', $request));
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->with(
+ \sprintf('%s/bin/ngrok-v3-stable-linux-amd64.tgz', VALET_ROOT_PATH),
+ 'ngrok-body-content'
+ )
+ ->once();
+
+ \Valet\Facades\Filesystem::ensureDirExists(\sprintf('%s/bin', VALET_ROOT_PATH), user());
+ $this->createFakeZip(\sprintf('%s/bin/ngrok-v3-stable-linux-amd64.tgz', VALET_ROOT_PATH));
+
+ $this->filesystem
+ ->shouldReceive('remove')
+ ->with(
+ \sprintf('%s/bin/ngrok-v3-stable-linux-amd64.tgz', VALET_ROOT_PATH)
+ )
+ ->once();
+
+ $this->ngrok->install();
+
+ unlink(\sprintf('%s/bin/ngrok-v3-stable-linux-amd64.tgz', VALET_ROOT_PATH));
+ unlink(\sprintf('%s/bin/sample_file', VALET_ROOT_PATH));
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+ $content = $output->fetch();
+ $this->assertStringContainsString('Ngrok', $content);
+ $this->assertStringContainsString('Installing', $content);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillNotOverrideWhenAlreadyInstalled(): void
+ {
+ Writer::fake();
+
+ $request = Mockery::mock(Request::class);
+ swap(Request::class, $request);
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(\sprintf('%s/bin/ngrok', VALET_ROOT_PATH))
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldNotReceive('ensureDirExists');
+
+ $request->shouldNotReceive('get')
+ ->with('https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz');
+
+ $request->shouldNotReceive('send');
+
+ $this->filesystem
+ ->shouldNotReceive('putAsUser');
+
+ $this->filesystem
+ ->shouldNotReceive('remove');
+
+ $this->ngrok->install();
+
+ /** @var BufferedOutput $output */
+ $output = Writer::output();
+ $content = $output->fetch();
+ $this->assertStringNotContainsString('Ngrok', $content);
+ $this->assertStringNotContainsString('Installing', $content);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillFetchCurrentTunnelUrl(): void
+ {
+ $request = Mockery::mock(Request::class);
+ swap(Request::class, $request);
+ Httpful::register(Mime::JSON, new JsonHandler(array('decode_as_array' => false)));
+
+ $request->shouldReceive('get')
+ ->once()
+ ->with('http://127.0.0.1:4040/api/tunnels')
+ ->andReturnSelf();
+
+ $mockResponse = $this->getFile('ngrok_response.json');
+ $request->shouldReceive('send')
+ ->once()
+ ->withNoArgs()
+ ->andReturn(new Response((string) $mockResponse, "HTTP/1.1 200 OK\r\n
+Content-Type: application/json\r\n
+Date: Tue, 14 May 2024 12:13:28 GMT\r\n
+Content-Length: 477
+", $request));
+
+ $output = $this->ngrok->currentTunnelUrl();
+
+ $this->assertSame('https://33e2-2405-201-2024-a899-720-a588-f13d-82fe.ngrok-free.app', $output);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillSetNgrokAuthToken(): void
+ {
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->with(
+ \sprintf('%s/bin/ngrok config add-authtoken test-token', VALET_ROOT_PATH)
+ );
+
+ $this->ngrok->setAuthToken('test-token');
+ }
+
+ private function createFakeZip(string $file): void
+ {
+ $zip = new \ZipArchive();
+ $zip->open($file, \ZipArchive::CREATE);
+ $zip->addFromString('sample_file', 'sample content');
+ $zip->close();
+ }
+
+ private function getFile(string $fileName): string|false
+ {
+ return file_get_contents(__DIR__ . '/MockResponse/' . $fileName);
+ }
+}
diff --git a/tests/Unit/PhpFpmTest.php b/tests/Unit/PhpFpmTest.php
new file mode 100644
index 0000000..ca8b25a
--- /dev/null
+++ b/tests/Unit/PhpFpmTest.php
@@ -0,0 +1,656 @@
+config = Mockery::mock(Configuration::class);
+ $this->commandLine = Mockery::mock(CommandLine::class);
+ $this->packageManager = Mockery::mock(PackageManager::class);
+ $this->serviceManager = Mockery::mock(ServiceManager::class);
+ $this->filesystem = Mockery::mock(Filesystem::class);
+ $this->site = Mockery::mock(Site::class);
+ $this->nginx = Mockery::mock(Nginx::class);
+
+ $this->phpFpm = new PhpFpm(
+ $this->config,
+ $this->packageManager,
+ $this->serviceManager,
+ $this->commandLine,
+ $this->filesystem,
+ $this->site,
+ $this->nginx
+ );
+ }
+
+ public function versionProvider(): array
+ {
+ return [
+ [
+ '8.2',
+ '8.2',
+ 'valet82.sock',
+ '8.0',
+ ],
+ [
+ '82',
+ '8.2',
+ 'valet82.sock',
+ '8.0',
+ ],
+ [
+ 'php-8.2',
+ '8.2',
+ 'valet82.sock',
+ '8.0',
+ ],
+ [
+ 'php8.2',
+ '8.2',
+ 'valet82.sock',
+ '8.0',
+ ],
+ [
+ 'php@8.2',
+ '8.2',
+ 'valet82.sock',
+ '8.0',
+ ],
+ [
+ '8.3',
+ '8.3',
+ 'valet83.sock',
+ '8.0',
+ ],
+ [
+ '83',
+ '8.3',
+ 'valet83.sock',
+ '8.0',
+ ],
+ [
+ 'php-8.3',
+ '8.3',
+ 'valet83.sock',
+ '8.0',
+ ],
+ [
+ 'php8.3',
+ '8.3',
+ 'valet83.sock',
+ '8.0',
+ ],
+ [
+ 'php@8.3',
+ '8.3',
+ 'valet83.sock',
+ '8.0',
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider versionProvider
+ */
+ public function itWillInstallSuccessfully(
+ string $version,
+ string $expectedVersion,
+ string $expectedSocketFileName
+ ): void {
+ $fpmName = \sprintf('php%s-fpm', $expectedVersion);
+ $prefix = \sprintf('php%s-', $expectedVersion);
+ $this->packageManager
+ ->shouldReceive('getPhpFpmName')
+ ->times(3)
+ ->with($expectedVersion)
+ ->andReturn($fpmName);
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->once()
+ ->with($fpmName)
+ ->andReturnFalse();
+
+ $this->packageManager
+ ->shouldReceive('ensureInstalled')
+ ->once()
+ ->with($fpmName)
+ ->andReturnFalse();
+
+ $this->packageManager
+ ->shouldReceive('getPhpExtensionPrefix')
+ ->once()
+ ->with($expectedVersion)
+ ->andReturn($prefix);
+
+ $this->packageManager
+ ->shouldReceive('ensureInstalled')
+ ->once()
+ ->with(
+ \sprintf(
+ '%1$scli %1$smysql %1$sgd %1$szip %1$sxml %1$scurl %1$smbstring %1$spgsql %1$sintl %1$sposix',
+ $prefix
+ )
+ );
+
+ $this->serviceManager
+ ->shouldReceive('enable')
+ ->once()
+ ->with($fpmName);
+
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with('/var/log', user());
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_ROOT_PATH . '/cli/stubs/fpm.conf')
+ ->andReturn('fpm.conf content VALET_USER VALET_GROUP VALET_FPM_SOCKET_FILE');
+
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->once()
+ ->with('/etc/php/' . $expectedVersion . '/fpm/pool.d')
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->once()
+ ->with(
+ '/etc/php/' . $expectedVersion . '/fpm/pool.d/valet.conf',
+ \sprintf('fpm.conf content %s %s %s', user(), group(), VALET_HOME_PATH . '/' . $expectedSocketFileName),
+ )
+ ->andReturn('fpm.conf content VALET_USER VALET_GROUP VALET_FPM_SOCKET_FILE');
+
+ $this->serviceManager
+ ->shouldReceive('restart')
+ ->once()
+ ->with($fpmName);
+
+ $this->phpFpm->install($version);
+ }
+
+ /**
+ * @test
+ * @dataProvider versionProvider
+ */
+ public function itWillUninstallSuccessfully(string $version, string $expectedVersion): void
+ {
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->with('/etc/php/' . $expectedVersion . '/fpm/pool.d')
+ ->once()
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->with('/etc/php/' . $expectedVersion . '/fpm/pool.d/valet.conf')
+ ->once()
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->with('/etc/php/' . $expectedVersion . '/fpm/pool.d/valet.conf')
+ ->once();
+
+ $this->packageManager
+ ->shouldReceive('getPhpFpmName')
+ ->with($expectedVersion)
+ ->once()
+ ->andReturn('php' . $expectedVersion . '-fpm');
+
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->with('php' . $expectedVersion . '-fpm')
+ ->once();
+
+ $this->phpFpm->uninstall($version);
+ }
+
+ /**
+ * @test
+ * @dataProvider versionProvider
+ */
+ public function itWillSwitchVersionSuccessfully(
+ string $version,
+ string $expectedVersion,
+ string $expectedSocketFileName,
+ string $currentVersion
+ ): void {
+ Writer::fake();
+ $this->config
+ ->shouldReceive('get')
+ ->with('php_version', PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION)
+ ->once()
+ ->andReturn($currentVersion);
+
+ $fpmName = \sprintf('php%s-fpm', $expectedVersion);
+ $prefix = \sprintf('php%s-', $expectedVersion);
+ $this->packageManager
+ ->shouldReceive('getPhpFpmName')
+ ->times(6)
+ ->with($expectedVersion)
+ ->andReturn($fpmName);
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->once()
+ ->with($fpmName)
+ ->andReturnFalse();
+
+ $this->packageManager
+ ->shouldReceive('ensureInstalled')
+ ->once()
+ ->with($fpmName)
+ ->andReturnFalse();
+
+ $this->packageManager
+ ->shouldReceive('getPhpExtensionPrefix')
+ ->once()
+ ->with($expectedVersion)
+ ->andReturn($prefix);
+
+ $this->packageManager
+ ->shouldReceive('ensureInstalled')
+ ->once()
+ ->with(
+ \sprintf(
+ '%1$scli %1$smysql %1$sgd %1$szip %1$sxml %1$scurl %1$smbstring %1$spgsql %1$sintl %1$sposix',
+ $prefix
+ )
+ );
+
+ $this->serviceManager
+ ->shouldReceive('enable')
+ ->once()
+ ->with($fpmName);
+
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with('/var/log', user());
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_ROOT_PATH . '/cli/stubs/fpm.conf')
+ ->andReturn('fpm.conf content VALET_USER VALET_GROUP VALET_FPM_SOCKET_FILE');
+
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->once()
+ ->with(
+ '/etc/php/' . $expectedVersion . '/fpm/pool.d',
+ )
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->once()
+ ->with(
+ '/etc/php/' . $expectedVersion . '/fpm/pool.d/valet.conf',
+ \sprintf('fpm.conf content %s %s %s', user(), group(), VALET_HOME_PATH . '/' . $expectedSocketFileName),
+ )
+ ->andReturn('fpm.conf content VALET_USER VALET_GROUP VALET_FPM_SOCKET_FILE');
+
+ $this->serviceManager
+ ->shouldReceive('restart')
+ ->once()
+ ->with($fpmName);
+
+ $this->serviceManager
+ ->expects('disabled')
+ ->once()
+ ->with($fpmName)
+ ->andReturnTrue();
+
+ $this->serviceManager
+ ->expects('enable')
+ ->once()
+ ->with($fpmName);
+
+ $this->config
+ ->shouldReceive('set')
+ ->once()
+ ->with('php_version', $expectedVersion);
+
+ $configuredSites = [
+ 'mails.test' => [
+ 'socket_file' => 'valet74.sock',
+ ],
+ 'site1.test' => [
+ 'socket_file' => 'valet73.sock',
+ ],
+ 'valetsite.test' => [
+ 'socket_file' => 'valet70.sock',
+ ],
+ ];
+ $this->nginx
+ ->shouldReceive('configuredSites')
+ ->twice()
+ ->andReturn(collect(array_keys($configuredSites)));
+
+ foreach ($configuredSites as $configuredSite => $siteData) {
+ $this->filesystem
+ ->shouldReceive('get')
+ ->twice()
+ ->with(VALET_HOME_PATH . '/Nginx/' . $configuredSite)
+ ->andReturn('content unix:' . $siteData['socket_file']);
+
+ $this->filesystem
+ ->shouldReceive('put')
+ ->once()
+ ->with(
+ VALET_HOME_PATH . '/Nginx/' . $configuredSite,
+ 'content unix:' . VALET_HOME_PATH . '/' . $expectedSocketFileName
+ );
+ }
+
+ $this->config
+ ->shouldReceive('get')
+ ->with('php_version', PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION)
+ ->once()
+ ->andReturn($expectedVersion);
+
+ $this->packageManager
+ ->shouldReceive('getPhpFpmName')
+ ->with($currentVersion)
+ ->once()
+ ->andReturn('php' . $currentVersion . '-fpm');
+
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->with('php' . $currentVersion . '-fpm')
+ ->once();
+
+ $this->nginx
+ ->shouldReceive('installServer')
+ ->once()
+ ->andReturn($expectedVersion);
+
+ $this->nginx
+ ->shouldReceive('restart')
+ ->once();
+
+ $this->serviceManager
+ ->shouldReceive('printStatus')
+ ->with($fpmName)
+ ->once();
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->with('update-alternatives --set php /usr/bin/php' . $expectedVersion)
+ ->once();
+
+ $this->phpFpm->switchVersion($version, true);
+ }
+
+ /**
+ * @test
+ * @dataProvider versionProvider
+ */
+ public function itWillRestartSuccessfully(string $version, string $expectedVersion): void
+ {
+ $fpmName = 'php' . $expectedVersion . '-fpm';
+
+ $this->packageManager
+ ->shouldReceive('getPhpFpmName')
+ ->with($expectedVersion)
+ ->andReturn($fpmName);
+
+ $this->serviceManager
+ ->shouldReceive('restart')
+ ->with($fpmName)
+ ->once();
+
+ $this->phpFpm->restart($expectedVersion);
+ }
+
+ /**
+ * @test
+ * @dataProvider versionProvider
+ */
+ public function itWillStopSuccessfully(string $version, string $expectedVersion): void
+ {
+ $fpmName = 'php' . $expectedVersion . '-fpm';
+
+ $this->packageManager
+ ->shouldReceive('getPhpFpmName')
+ ->with($expectedVersion)
+ ->andReturn($fpmName);
+
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->with($fpmName)
+ ->once();
+
+ $this->phpFpm->stop($expectedVersion);
+ }
+
+ /**
+ * @test
+ * @dataProvider versionProvider
+ */
+ public function itWillGetStatusSuccessfully(string $version, string $expectedVersion): void
+ {
+ $fpmName = 'php' . $expectedVersion . '-fpm';
+
+ $this->packageManager
+ ->shouldReceive('getPhpFpmName')
+ ->with($expectedVersion)
+ ->andReturn($fpmName);
+
+ $this->serviceManager
+ ->shouldReceive('printStatus')
+ ->with($fpmName)
+ ->once();
+
+ $this->phpFpm->status($expectedVersion);
+ }
+
+ public function socketFileVersionProvider(): array
+ {
+ return [
+ [
+ '8.2',
+ 'valet82.sock',
+ ],
+ [
+ '8.1',
+ 'valet81.sock',
+ ],
+ [
+ '8.3',
+ 'valet83.sock',
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider socketFileVersionProvider
+ */
+ public function itWillGetSocketFileName(string $version, string $expectedSocket): void
+ {
+ $socketFile = $this->phpFpm->socketFileName($version);
+
+ $this->assertSame($expectedSocket, $socketFile);
+ }
+
+ /**
+ * @test
+ * @dataProvider versionProvider
+ */
+ public function itWillNormalizePhpVersion(string $phpVersion, string $expectedVersion): void
+ {
+ $normalizedVersion = $this->phpFpm->normalizePhpVersion($phpVersion);
+
+ $this->assertSame($expectedVersion, $normalizedVersion);
+ }
+
+ public function invalidVersionProvider(): array
+ {
+ return [
+ [
+ 'invalid',
+ ],
+ [
+ '8',
+ ],
+ [
+ 'invalid-8.0',
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider invalidVersionProvider
+ */
+ public function itWillReturnBlankStringWhenInvalidStringGiven(string $version): void
+ {
+ $normalizedVersion = $this->phpFpm->normalizePhpVersion($version);
+
+ $this->assertSame('', $normalizedVersion);
+ }
+
+ public function executableVersionProvider(): array
+ {
+ return [
+ [
+ '8.1',
+ 'valet81.sock',
+ ],
+ [
+ '8.2',
+ 'valet82.sock',
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider executableVersionProvider
+ */
+ public function itWillGetExecutablePath(string $version): void
+ {
+ $devTools = Mockery::mock(DevTools::class);
+ swap(DevTools::class, $devTools);
+
+ $devTools->shouldReceive('getBin')
+ ->with('php' . $version, ['/usr/local/bin/php'])
+ ->once()
+ ->andReturn('/usr/bin/php' . $version);
+
+ $binFile = $this->phpFpm->getPhpExecutablePath($version);
+
+ $this->assertSame('/usr/bin/php' . $version, $binFile);
+ }
+
+ /**
+ * @test
+ * @dataProvider executableVersionProvider
+ */
+ public function itWillGetFpmSocketFile(string $version, string $expectedSocketFile): void
+ {
+ $socketFile = $this->phpFpm->fpmSocketFile($version);
+
+ $this->assertSame(VALET_HOME_PATH . '/' . $expectedSocketFile, $socketFile);
+ }
+
+ public function versionDataProvider(): array
+ {
+ return [
+ [
+ '8.2',
+ ],
+ [
+ '8.3',
+ ]
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider versionDataProvider
+ */
+ public function itWillValidateVersion(string $version): void
+ {
+ $isValid = $this->phpFpm->validateVersion($version);
+
+ $this->assertTrue($isValid);
+ }
+
+ public function deprecatedVersionDataProvider(): array
+ {
+ return [
+ [
+ '7.0',
+ ],
+ [
+ '7.1',
+ ],
+ [
+ '7.2',
+ ],
+ [
+ '7.3',
+ ],
+ [
+ '7.4',
+ ],
+ [
+ '8.0',
+ ],
+ [
+ '8.1',
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider deprecatedVersionDataProvider
+ */
+ public function itWillValidateDeprecatedVersion(string $version): void
+ {
+ $isValid = $this->phpFpm->validateVersion($version);
+
+ $this->assertFalse($isValid);
+ }
+}
diff --git a/tests/Unit/RequirementsTest.php b/tests/Unit/RequirementsTest.php
new file mode 100644
index 0000000..93c9fc9
--- /dev/null
+++ b/tests/Unit/RequirementsTest.php
@@ -0,0 +1,72 @@
+commandLine = Mockery::mock(CommandLine::class);
+
+ $this->requirements = new Requirements(
+ $this->commandLine
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillVerifyIfSELinuxIsEnabled(): void
+ {
+ $this->commandLine
+ ->shouldReceive('run')
+ ->with('sestatus')
+ ->once()
+ ->andReturn('@SELinux status: disabled');
+
+ $this->requirements->setIgnoreSELinux(false);
+ $this->requirements->check();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillThrowExceptionWhenSELinuxIsEnabledAndEnforcing(): void
+ {
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('SELinux is in enforcing mode');
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->with('sestatus')
+ ->once()
+ ->andReturn("SELinux status: enabled\nCurrent mode: enforcing");
+
+ $this->requirements->setIgnoreSELinux(false);
+ $this->requirements->check();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillSkipCheckingSELinuxWhenIgnored(): void
+ {
+ $this->commandLine
+ ->shouldNotReceive('run')
+ ->with('sestatus');
+
+ $this->requirements->setIgnoreSELinux();
+ $this->requirements->check();
+ }
+}
diff --git a/tests/Unit/SiteIsolateTest.php b/tests/Unit/SiteIsolateTest.php
new file mode 100644
index 0000000..d8bf53e
--- /dev/null
+++ b/tests/Unit/SiteIsolateTest.php
@@ -0,0 +1,344 @@
+packageManager = Mockery::mock(PackageManager::class);
+ $this->config = Mockery::mock(Configuration::class);
+ $this->filesystem = Mockery::mock(Filesystem::class);
+ $this->siteSecure = Mockery::mock(SiteSecure::class);
+ $this->site = Mockery::mock(Site::class);
+
+ $this->siteIsolate = new SiteIsolate(
+ $this->packageManager,
+ $this->config,
+ $this->filesystem,
+ $this->siteSecure,
+ $this->site
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillIsolateDirectory(): void
+ {
+ $phpFpm = Mockery::mock(PhpFpm::class);
+ swap(PhpFpm::class, $phpFpm);
+ $nginx = Mockery::mock(Nginx::class);
+ swap(Nginx::class, $nginx);
+ $devTools = Mockery::mock(DevTools::class);
+ swap(DevTools::class, $devTools);
+
+ $this->site
+ ->shouldReceive('getSiteUrl')
+ ->with('site')
+ ->once()
+ ->andReturn('site.test');
+ $phpFpm->shouldReceive('normalizePhpVersion')
+ ->with('7.2')
+ ->once()
+ ->andReturn('7.2');
+
+ $this->packageManager
+ ->shouldReceive('getPhpFpmName')
+ ->with('7.2')
+ ->once()
+ ->andReturn('php7.2-fpm');
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->with('php7.2-fpm')
+ ->once()
+ ->andReturnFalse();
+
+ $phpFpm->shouldReceive('install')
+ ->with('7.2')
+ ->once();
+
+ // Isolated PHP Version
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/site.test')
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/site.test')
+ ->andReturn("# ISOLATED_PHP_VERSION=7.1\nNginx content");
+ // Isolated PHP Version END
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_ROOT_PATH . '/cli/stubs/secure.isolated.valet.conf')
+ ->andReturn('secure stub content');
+
+ $phpFpm->shouldReceive('fpmSocketFile')
+ ->with('7.2')
+ ->once()
+ ->andReturn('/var/run/php/php7.2-fpm.sock');
+
+ $this->siteSecure
+ ->shouldReceive('secure')
+ ->once()
+ ->with('site.test', 'secure stub content');
+
+ $phpFpm->shouldReceive('stopIfUnused')
+ ->with('7.1')
+ ->once();
+
+ $phpFpm->shouldReceive('restart')
+ ->with('7.2')
+ ->once();
+
+ $nginx->shouldReceive('restart')
+ ->withNoArgs()
+ ->once();
+
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('domain')
+ ->andReturn('test');
+
+ $devTools->shouldReceive('getBin')
+ ->once()
+ ->with('php7.2', ['/usr/local/bin/php'])
+ ->andReturn('/usr/bin/php7.2');
+
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('isolated_versions', [])
+ ->andReturn([]);
+
+ $this->config
+ ->shouldReceive('set')
+ ->once()
+ ->with(
+ 'isolated_versions',
+ [
+ 'site' => '/usr/bin/php7.2',
+ ]
+ );
+
+ $return = $this->siteIsolate->isolateDirectory('site', '7.2', true);
+
+ $this->assertTrue($return);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUnIsolateDirectory(): void
+ {
+ $phpFpm = Mockery::mock(PhpFpm::class);
+ swap(PhpFpm::class, $phpFpm);
+ $nginx = Mockery::mock(Nginx::class);
+ swap(Nginx::class, $nginx);
+
+ $this->site
+ ->shouldReceive('getSiteUrl')
+ ->with('site')
+ ->once()
+ ->andReturn('site.test');
+
+ // Isolated PHP Version
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/site.test')
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/site.test')
+ ->andReturn("# ISOLATED_PHP_VERSION=7.2\nNginx content");
+ // Isolated PHP Version END
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Certificates/site.test.crt')
+ ->andReturnTrue();
+
+ $this->siteSecure
+ ->shouldReceive('buildSecureNginxServer')
+ ->once()
+ ->with('site.test')
+ ->andReturn('site conf');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/site.test', 'site conf');
+
+ $phpFpm->shouldReceive('stopIfUnused')
+ ->with('7.2')
+ ->once();
+
+ $nginx->shouldReceive('restart')
+ ->withNoArgs()
+ ->once();
+
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('domain')
+ ->andReturn('test');
+
+ $this->config
+ ->shouldReceive('get')
+ ->with('isolated_versions', [])
+ ->once()
+ ->andReturn(['site' => '/usr/local/bin/php7.2']);
+
+ $this->config
+ ->shouldReceive('set')
+ ->with('isolated_versions', [])
+ ->once();
+
+ $this->siteIsolate->unIsolateDirectory('site');
+ }
+
+ /**
+ * @test
+ */
+ public function itWillListIsolatedDirectories(): void
+ {
+ $nginx = Mockery::mock(Nginx::class);
+ swap(Nginx::class, $nginx);
+ $phpFpm = Mockery::mock(PhpFpm::class);
+ swap(PhpFpm::class, $phpFpm);
+
+ $this->siteSecure
+ ->shouldReceive('secured')
+ ->withNoArgs()
+ ->once()
+ ->andReturn(collect([
+ 'site1.test'
+ ]));
+
+ $dummySites = ['site1.test', 'site2.test'];
+ $nginx->shouldReceive('configuredSites')
+ ->withNoArgs()
+ ->once()
+ ->andReturn(collect($dummySites));
+
+ foreach ($dummySites as $dummySite) {
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/' . $dummySite)
+ ->andReturn('ISOLATED_PHP_VERSION');
+
+ // Isolated PHP Version
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/' . $dummySite)
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/' . $dummySite)
+ ->andReturn("# ISOLATED_PHP_VERSION=7.2\nNginx content");
+ // Isolated PHP Version END
+
+ $phpFpm->shouldReceive('normalizePhpVersion')
+ ->with('7.2')
+ ->once()
+ ->andReturn('7.2');
+ }
+
+ $response = $this->siteIsolate->isolatedDirectories();
+ $this->assertSame([
+ [
+ 'url' => 'https://site1.test',
+ 'secured' => '✓',
+ 'version' => '7.2',
+ ],
+ [
+ 'url' => 'http://site2.test',
+ 'secured' => '✕',
+ 'version' => '7.2',
+ ],
+ ], $response->all());
+ }
+
+ /**
+ * @test
+ */
+ public function itWillGetIsolatedPhpVersion(): void
+ {
+ $site = 'site.test';
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/' . $site)
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/' . $site)
+ ->andReturn("# ISOLATED_PHP_VERSION=7.2\nNginx content");
+
+ $version = $this->siteIsolate->isolatedPhpVersion($site);
+ $this->assertSame('7.2', $version);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillReturnNullWhenVersionNotFound(): void
+ {
+ $site = 'site.test';
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/' . $site)
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/' . $site)
+ ->andReturn('Nginx content');
+
+ $version = $this->siteIsolate->isolatedPhpVersion($site);
+ $this->assertNull($version);
+ }
+}
diff --git a/tests/Unit/SiteLinkTest.php b/tests/Unit/SiteLinkTest.php
new file mode 100644
index 0000000..0089222
--- /dev/null
+++ b/tests/Unit/SiteLinkTest.php
@@ -0,0 +1,160 @@
+filesystem = Mockery::mock(Filesystem::class);
+ $this->config = Mockery::mock(Configuration::class);
+ $this->siteSecure = Mockery::mock(SiteSecure::class);
+
+ $this->siteLink = new SiteLink(
+ $this->filesystem,
+ $this->config,
+ $this->siteSecure
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillLinkSite(): void
+ {
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Sites', user());
+
+ $this->config
+ ->shouldReceive('addPath')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Sites', true);
+
+ $this->filesystem
+ ->shouldReceive('symlinkAsUser')
+ ->once()
+ ->with('/test/home/path', VALET_HOME_PATH . '/Sites/path');
+
+ $host = $this->siteLink->link('/test/home/path', 'path');
+
+ $this->assertEquals(VALET_HOME_PATH . '/Sites/path', $host);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUnlinkSite(): void
+ {
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Sites/path')
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Sites/path');
+
+ $this->siteLink->unlink('path');
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillNotUnlinkWhenSiteNotLinked(): void
+ {
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Sites/path')
+ ->andReturnFalse();
+
+ $this->filesystem
+ ->shouldNotReceive('unlink');
+
+ $this->siteLink->unlink('path');
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillLoadLinks(): void
+ {
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Certificates', user());
+
+ $this->siteSecure
+ ->shouldReceive('secured')
+ ->once()
+ ->withNoArgs()
+ ->andReturn(collect([
+ 'site1.test'
+ ]));
+
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('domain')
+ ->andReturn('test');
+
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('port', 80)
+ ->andReturn(80);
+
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('https_port', 443)
+ ->andReturn(443);
+
+ $this->filesystem
+ ->shouldReceive('scandir')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Sites')
+ ->andReturn(['path']);
+
+ $this->filesystem
+ ->shouldReceive('readLink')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Sites/path')
+ ->andReturn('/test/home/path');
+
+ $links = $this->siteLink->links();
+
+ $this->assertSame([
+ 'path' => [
+ 'url' => 'http://path.test',
+ 'secured' => '✕',
+ 'path' => '/test/home/path',
+ ],
+ ], $links->toArray());
+ }
+}
diff --git a/tests/Unit/SiteProxyTest.php b/tests/Unit/SiteProxyTest.php
new file mode 100644
index 0000000..f15181b
--- /dev/null
+++ b/tests/Unit/SiteProxyTest.php
@@ -0,0 +1,115 @@
+filesystem = Mockery::mock(Filesystem::class);
+ $this->config = Mockery::mock(Configuration::class);
+ $this->siteSecure = Mockery::mock(SiteSecure::class);
+
+ $this->siteProxy = new SiteProxy(
+ $this->filesystem,
+ $this->config,
+ $this->siteSecure
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillCreateProxy(): void
+ {
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('domain')
+ ->andReturn('test');
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_ROOT_PATH . '/cli/stubs/secure.proxy.valet.conf')
+ ->andReturn('VALET_PROXY_HOST');
+
+ $this->siteSecure
+ ->shouldReceive('secure')
+ ->once()
+ ->with('site.test', 'http://localhost:8000');
+
+ $this->siteProxy->proxyCreate('site', 'http://localhost:8000', true);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillLoadProxies(): void
+ {
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('domain')
+ ->andReturn('test');
+
+ $this->siteSecure
+ ->shouldReceive('secured')
+ ->once()
+ ->withNoArgs()
+ ->andReturn(collect([
+ 'site1.test'
+ ]));
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx')
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('scandir')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx')
+ ->andReturn([
+ 'site1.test'
+ ]);
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/site1.test')
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/site1.test')
+ ->andReturn('proxy_pass http://localhost:8025;');
+
+ $response = $this->siteProxy->proxies();
+
+ $this->assertSame([
+ 'site1.test' => [
+ 'url' => 'https://site1.test',
+ 'secured' => '✓',
+ 'path' => 'http://localhost:8025',
+ ],
+ ], $response->all());
+ }
+}
diff --git a/tests/Unit/SiteSecureTest.php b/tests/Unit/SiteSecureTest.php
new file mode 100644
index 0000000..6232237
--- /dev/null
+++ b/tests/Unit/SiteSecureTest.php
@@ -0,0 +1,367 @@
+filesystem = Mockery::mock(Filesystem::class);
+ $this->commandLine = Mockery::mock(CommandLine::class);
+ $this->config = Mockery::mock(Configuration::class);
+
+ $this->siteSecure = new SiteSecure(
+ $this->filesystem,
+ $this->commandLine,
+ $this->config
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillSecureNewDomain(): void
+ {
+ $phpFpm = Mockery::mock(PhpFpm::class);
+ swap(PhpFpm::class, $phpFpm);
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/site.test')
+ ->andReturnFalse();
+
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/CA', user());
+
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Certificates', user());
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/CA/ValetLinuxCASelfSigned.pem')
+ ->andReturnFalse();
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->twice()
+ ->with(VALET_HOME_PATH . '/CA/ValetLinuxCASelfSigned.key')
+ ->andReturnFalse();
+
+ $this->filesystem
+ ->shouldReceive('remove')
+ ->once()
+ ->with('/usr/local/share/ca-certificates/ValetLinuxCASelfSigned.pem.crt')
+ ->andReturnFalse();
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->with('sudo update-ca-certificates');
+
+ $caExpireInDate = (new \DateTime())->diff(new \DateTime("+20 years"));
+ $expiryInDays = (int)$caExpireInDate->format('%a'); // 20 years in days
+ $subject = sprintf(
+ '/C=/ST=/O=%s/localityName=/commonName=%s/organizationalUnitName=Developers/emailAddress=%s/',
+ 'Valet Linux CA Self Signed Organization',
+ 'Valet Linux CA Self Signed CN',
+ 'certificate@valet.linux',
+ );
+ $this->commandLine
+ ->shouldReceive('runAsUser')
+ ->once()
+ ->with(sprintf(
+ 'openssl req -new -newkey rsa:2048 -days %s -nodes -x509 -subj "%s" -keyout "%s" -out "%s"',
+ $expiryInDays,
+ $subject,
+ VALET_HOME_PATH . '/CA/ValetLinuxCASelfSigned.key',
+ VALET_HOME_PATH . '/CA/ValetLinuxCASelfSigned.pem'
+ ));
+
+ $this->filesystem
+ ->shouldReceive('copy')
+ ->once()
+ ->with(
+ VALET_HOME_PATH . '/CA/ValetLinuxCASelfSigned.pem',
+ '/usr/local/share/ca-certificates/ValetLinuxCASelfSigned.pem.crt'
+ );
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->with('sudo update-ca-certificates');
+
+ $this->commandLine
+ ->shouldReceive('runAsUser')
+ ->once()
+ ->with(sprintf(
+ 'certutil -d sql:$HOME/.pki/nssdb -A -t TC -n "%s" -i "%s"',
+ 'Valet Linux CA Self Signed Organization',
+ VALET_HOME_PATH . '/CA/ValetLinuxCASelfSigned.pem'
+ ));
+
+ $this->commandLine
+ ->shouldReceive('runAsUser')
+ ->once()
+ ->with(sprintf(
+ 'certutil -d $HOME/.mozilla/firefox/*.default -A -t TC -n "%s" -i "%s"',
+ 'Valet Linux CA Self Signed Organization',
+ VALET_HOME_PATH . '/CA/ValetLinuxCASelfSigned.pem'
+ ));
+
+ $this->commandLine
+ ->shouldReceive('runAsUser')
+ ->once()
+ ->with(sprintf(
+ 'certutil -d $HOME/snap/firefox/common/.mozilla/firefox/*.default -A -t TC -n "%s" -i "%s"',
+ 'Valet Linux CA Self Signed Organization',
+ VALET_HOME_PATH . '/CA/ValetLinuxCASelfSigned.pem'
+ ));
+
+ $certificateExpireInDate = (new \DateTime())->diff(new \DateTime("+1 year"));
+ $certificateExpireInDays = (int)$certificateExpireInDate->format('%a'); // 20 years in days
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_ROOT_PATH . '/cli/stubs/openssl.conf')
+ ->andReturn('SSL conf');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Certificates/site.test.conf', 'SSL conf');
+
+ $this->commandLine
+ ->shouldReceive('runAsUser')
+ ->once()
+ ->with(\sprintf(
+ 'openssl genrsa -out %s 2048',
+ VALET_HOME_PATH . '/Certificates/site.test.key'
+ ));
+
+ $subject = sprintf(
+ '/C=/ST=/O=/localityName=/commonName=%s/organizationalUnitName=/emailAddress=%s/',
+ 'site.test',
+ 'certificate@valet.linux',
+ );
+ $this->commandLine
+ ->shouldReceive('runAsUser')
+ ->once()
+ ->with(\sprintf(
+ 'openssl req -new -key %s -out %s -subj "%s" -config %s',
+ VALET_HOME_PATH . '/Certificates/site.test.key',
+ VALET_HOME_PATH . '/Certificates/site.test.csr',
+ $subject,
+ VALET_HOME_PATH . '/Certificates/site.test.conf'
+ ));
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/CA/ValetLinuxCASelfSigned.srl')
+ ->andReturnFalse();
+
+ $caSrlParam = '-CAserial "' . VALET_HOME_PATH . '/CA/ValetLinuxCASelfSigned.srl" -CAcreateserial';
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->once()
+ ->with(sprintf(
+ 'openssl x509 -req -sha256 -days %s -CA "%s" -CAkey "%s" %s -in %s -out %s -extensions v3_req -extfile %s',
+ $certificateExpireInDays,
+ VALET_HOME_PATH . '/CA/ValetLinuxCASelfSigned.pem',
+ VALET_HOME_PATH . '/CA/ValetLinuxCASelfSigned.key',
+ $caSrlParam,
+ VALET_HOME_PATH . '/Certificates/site.test.csr',
+ VALET_HOME_PATH . '/Certificates/site.test.crt',
+ VALET_HOME_PATH . '/Certificates/site.test.conf'
+ ));
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with(VALET_ROOT_PATH . '/cli/stubs/secure.valet.conf')
+ ->andReturn('Nginx Conf');
+
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('port', 80)
+ ->andReturn(80);
+
+ $this->config
+ ->shouldReceive('get')
+ ->twice()
+ ->with('https_port', 443)
+ ->andReturn(443);
+
+ $phpFpm->shouldReceive('getCurrentVersion')
+ ->once()
+ ->withNoArgs()
+ ->andReturn('8.3');
+
+ $phpFpm->shouldReceive('fpmSocketFile')
+ ->once()
+ ->with('8.3')
+ ->andReturn(VALET_HOME_PATH . '/' . 'valet83.sock');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/site.test', 'Nginx Conf');
+
+ $this->siteSecure->secure('site.test');
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUnsecureSite(): void
+ {
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Certificates/site.test.crt')
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/site.test');
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Certificates/site.test.conf');
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Certificates/site.test.key');
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Certificates/site.test.csr');
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Certificates/site.test.crt');
+
+ $this->siteSecure->unsecure('site.test');
+ }
+
+ /**
+ * @test
+ */
+ public function itWillListSecuredSites(): void
+ {
+ $this->filesystem
+ ->shouldReceive('scandir')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Certificates')
+ ->andReturn([
+ 'site.test.crt',
+ 'site.test.csr',
+ 'site.test.key',
+ 'site.test.conf',
+ 'site2.test.crt',
+ 'site2.test.csr',
+ 'site2.test.key',
+ 'site2.test.conf',
+ ]);
+
+ $sites = $this->siteSecure->secured();
+
+ $this->assertSame(['site.test', 'site2.test'], $sites->toArray());
+ }
+
+ /**
+ * @test
+ */
+ public function itWillRegenerateSecuredSites(): void
+ {
+ $phpFpm = Mockery::mock(PhpFpm::class);
+ swap(PhpFpm::class, $phpFpm);
+
+ $this->filesystem
+ ->shouldReceive('scandir')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Certificates')
+ ->andReturn([
+ 'site.test.crt',
+ 'site.test.csr',
+ 'site.test.key',
+ 'site.test.conf',
+ 'site2.test.crt',
+ 'site2.test.csr',
+ 'site2.test.key',
+ 'site2.test.conf',
+ ]);
+ foreach (['site.test', 'site2.test'] as $site) {
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->with(VALET_ROOT_PATH . '/cli/stubs/secure.valet.conf')
+ ->once()
+ ->andReturn('Nginx Conf');
+
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('port', 80)
+ ->andReturn(80);
+
+ $this->config
+ ->shouldReceive('get')
+ ->twice()
+ ->with('https_port', 443)
+ ->andReturn(443);
+
+ $phpFpm->shouldReceive('getCurrentVersion')
+ ->once()
+ ->withNoArgs()
+ ->andReturn('8.3');
+
+ $phpFpm->shouldReceive('fpmSocketFile')
+ ->once()
+ ->with('8.3')
+ ->andReturn(VALET_HOME_PATH . '/' . 'valet83.sock');
+
+ $this->filesystem
+ ->shouldReceive('putAsUser')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Nginx/' . $site, 'Nginx Conf');
+ }
+
+ $this->siteSecure->regenerateSecuredSitesConfig();
+
+ $this->assertTrue(true);
+ }
+}
diff --git a/tests/Unit/SiteTest.php b/tests/Unit/SiteTest.php
new file mode 100644
index 0000000..b5298e2
--- /dev/null
+++ b/tests/Unit/SiteTest.php
@@ -0,0 +1,175 @@
+config = Mockery::mock(Configuration::class);
+ $this->commandLine = Mockery::mock(CommandLine::class);
+ $this->filesystem = Mockery::mock(Filesystem::class);
+
+ $this->site = new Site(
+ $this->config,
+ $this->commandLine,
+ $this->filesystem
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillPruneLinks(): void
+ {
+ $this->filesystem
+ ->shouldReceive('ensureDirExists')
+ ->with(VALET_HOME_PATH . '/Sites', user())
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('removeBrokenLinksAt')
+ ->with(VALET_HOME_PATH . '/Sites')
+ ->once();
+
+ $this->site->pruneLinks();
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillGetSiteUrl(): void
+ {
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('domain')
+ ->andReturn('test');
+
+ // servedSites
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('paths', [])
+ ->andReturn(['path1']);
+
+ $dummySites = ['test', 'site2'];
+ $this->filesystem
+ ->shouldReceive('scandir')
+ ->once()
+ ->with('path1')
+ ->andReturn($dummySites);
+
+ foreach ($dummySites as $dummySite) {
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->once()
+ ->with('path1/' . $dummySite)
+ ->andReturnTrue();
+ }
+
+ $nginxSites = ['proxy1', 'proxy2'];
+ $this->filesystem
+ ->shouldReceive('scandir')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Sites')
+ ->andReturn($nginxSites);
+
+ foreach ($nginxSites as $nginxSite) {
+ $this->filesystem
+ ->shouldReceive('realpath')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Sites/' . $nginxSite)
+ ->andReturn('/real/path/' . $nginxSite);
+ }
+
+ $output = $this->site->getSiteUrl('test');
+ $this->assertSame('test.test', $output);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillGetPhpRcVersion(): void
+ {
+ // servedSites
+ $this->config
+ ->shouldReceive('get')
+ ->once()
+ ->with('paths', [])
+ ->andReturn(['path1']);
+
+ $dummySites = ['test', 'site2'];
+ $this->filesystem
+ ->shouldReceive('scandir')
+ ->once()
+ ->with('path1')
+ ->andReturn($dummySites);
+
+ foreach ($dummySites as $dummySite) {
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->once()
+ ->with('path1/' . $dummySite)
+ ->andReturnTrue();
+ }
+
+ $nginxSites = ['proxy1', 'proxy2'];
+ $this->filesystem
+ ->shouldReceive('scandir')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Sites')
+ ->andReturn($nginxSites);
+
+ foreach ($nginxSites as $nginxSite) {
+ $this->filesystem
+ ->shouldReceive('realpath')
+ ->once()
+ ->with(VALET_HOME_PATH . '/Sites/' . $nginxSite)
+ ->andReturn('/real/path/' . $nginxSite);
+ }
+
+ $this->filesystem
+ ->shouldReceive('exists')
+ ->once()
+ ->with('path1/test/.valetphprc')
+ ->andReturnTrue();
+
+ $this->filesystem
+ ->shouldReceive('get')
+ ->once()
+ ->with('path1/test/.valetphprc')
+ ->andReturn(' 8.2 ');
+
+ $phpFpm = Mockery::mock(PhpFpm::class);
+ $phpFpm->shouldReceive('normalizePhpVersion')
+ ->once()
+ ->with('8.2')
+ ->andReturn('8.2');
+ swap(PhpFpm::class, $phpFpm);
+
+ $version = $this->site->phpRcVersion('test');
+ $this->assertSame('8.2', $version);
+ }
+}
diff --git a/tests/Unit/ValetRedisTest.php b/tests/Unit/ValetRedisTest.php
new file mode 100644
index 0000000..bb37e55
--- /dev/null
+++ b/tests/Unit/ValetRedisTest.php
@@ -0,0 +1,136 @@
+packageManager = Mockery::mock(PackageManager::class);
+ $this->serviceManager = Mockery::mock(ServiceManager::class);
+ $this->commandLine = Mockery::mock(CommandLine::class);
+
+ $this->redis = new ValetRedis(
+ $this->packageManager,
+ $this->serviceManager,
+ $this->commandLine
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillInstallSuccessfully(): void
+ {
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->once()
+ ->with('redis')
+ ->andReturn('redis-server');
+
+ $this->packageManager
+ ->shouldReceive('ensureInstalled')
+ ->once()
+ ->with('redis-server');
+
+ $this->serviceManager
+ ->shouldReceive('enable')
+ ->once()
+ ->with('redis-server');
+
+ $this->redis->install();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillValidateIfPackageIsInstalled(): void
+ {
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->once()
+ ->with('redis')
+ ->andReturn('redis-server');
+
+ $this->packageManager
+ ->shouldReceive('installed')
+ ->once()
+ ->with('redis-server')
+ ->andReturnTrue();
+
+ $isInstalled = $this->redis->installed();
+ $this->assertTrue($isInstalled);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillRestartServiceSuccessfully(): void
+ {
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->once()
+ ->with('redis')
+ ->andReturn('redis-server');
+
+ $this->serviceManager
+ ->shouldReceive('restart')
+ ->once()
+ ->with('redis-server');
+
+ $this->redis->restart();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillStopServiceSuccessfully(): void
+ {
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->once()
+ ->with('redis')
+ ->andReturn('redis-server');
+
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->once()
+ ->with('redis-server');
+
+ $this->redis->stop();
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUninstallServiceSuccessfully(): void
+ {
+ $this->packageManager
+ ->shouldReceive('packageName')
+ ->once()
+ ->with('redis')
+ ->andReturn('redis-server');
+
+ $this->serviceManager
+ ->shouldReceive('stop')
+ ->once()
+ ->with('redis-server');
+
+ $this->redis->uninstall();
+ }
+}
diff --git a/tests/Unit/ValetTest.php b/tests/Unit/ValetTest.php
new file mode 100644
index 0000000..1b7dfee
--- /dev/null
+++ b/tests/Unit/ValetTest.php
@@ -0,0 +1,218 @@
+commandLine = Mockery::mock(CommandLine::class);
+ $this->filesystem = Mockery::mock(Filesystem::class);
+
+ $this->valet = new Valet(
+ $this->commandLine,
+ $this->filesystem
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itWillLinkValetBinFileToSystemPath(): void
+ {
+ $this->commandLine
+ ->shouldReceive('run')
+ ->with('ln -snf ' . realpath(VALET_ROOT_PATH . '/valet') . ' /usr/local/bin/valet')
+ ->once();
+
+ $this->valet->symlinkToUsersBin();
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillLinkPhpBinFileToSystemPath(): void
+ {
+ $config = Mockery::mock(Configuration::class);
+ swap(Configuration::class, $config);
+ $execPath = $_SERVER['_'] ?? '/usr/bin/php';
+
+ $this->filesystem
+ ->shouldReceive('realpath')
+ ->with($execPath)
+ ->andReturn('/usr/bin/php');
+
+ $config->shouldReceive('set')
+ ->with('fallback_binary', '/usr/bin/php')
+ ->once();
+
+ $this->commandLine
+ ->shouldReceive('run')
+ ->with('ln -snf ' . realpath(VALET_ROOT_PATH . '/php') . ' /usr/local/bin/php')
+ ->once();
+
+ $this->valet->symlinkPhpToUsersBin();
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillUninstallSuccessfully(): void
+ {
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->with('/usr/local/bin/valet')
+ ->once();
+
+ $this->filesystem
+ ->shouldReceive('unlink')
+ ->with('/usr/local/bin/php')
+ ->once();
+
+ $this->valet->uninstall();
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillLoadExtensionsFromHomeDir(): void
+ {
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->with(VALET_HOME_PATH . '/Extensions')
+ ->once()
+ ->andReturn(true);
+
+ $dummyExtensions = ['ext-1.php', 'ext-2.php'];
+ $this->filesystem
+ ->shouldReceive('scandir')
+ ->with(VALET_HOME_PATH . '/Extensions')
+ ->once()
+ ->andReturn($dummyExtensions);
+
+ foreach ($dummyExtensions as $dummyExtension) {
+ $this->filesystem
+ ->shouldReceive('isDir')
+ ->with($dummyExtension)
+ ->once()
+ ->andReturnFalse();
+ }
+
+ $extensions = $this->valet->extensions();
+
+ $this->assertSame([
+ VALET_HOME_PATH . '/Extensions/ext-1.php',
+ VALET_HOME_PATH . '/Extensions/ext-2.php',
+ ], $extensions);
+ }
+
+ public function versionDataProvider(): array
+ {
+ return [
+ [
+ '2.0.0',
+ true,
+ ],
+ [
+ 'v1.1.0',
+ false,
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider versionDataProvider
+ */
+ public function itWillCompareLatestVersion(string $version, bool $expectedResponse): void
+ {
+
+ $request = Mockery::mock(Request::class);
+ swap(Request::class, $request);
+ Httpful::register(Mime::JSON, new JsonHandler(array('decode_as_array' => false)));
+
+ $request->shouldReceive('get')
+ ->with('https://api.github.com/repos/genesisweb/valet-linux-plus/releases/latest')
+ ->once()
+ ->andReturnSelf();
+
+ $mockResponse = $this->getFile('github_response.json');
+ $request->shouldReceive('send')
+ ->once()
+ ->withNoArgs()
+ ->andReturn(new Response((string) $mockResponse, "HTTP/1.1 200 OK\r\n
+Content-Type: application/json\r\n
+Date: Tue, 14 May 2024 12:13:28 GMT\r\n
+Content-Length: 477
+", $request));
+
+ $isLatest = $this->valet->onLatestVersion($version);
+
+ $this->assertSame($expectedResponse, $isLatest);
+ }
+
+ /**
+ * @test
+ */
+ public function itWillGetLatestVersion(): void
+ {
+
+ $request = Mockery::mock(Request::class);
+ swap(Request::class, $request);
+ Httpful::register(Mime::JSON, new JsonHandler(array('decode_as_array' => false)));
+
+ $request->shouldReceive('get')
+ ->with('https://api.github.com/repos/genesisweb/valet-linux-plus/releases/latest')
+ ->once()
+ ->andReturnSelf();
+
+ $mockResponse = $this->getFile('github_response.json');
+ $request->shouldReceive('send')
+ ->once()
+ ->withNoArgs()
+ ->andReturn(new Response((string) $mockResponse, "HTTP/1.1 200 OK\r\n
+Content-Type: application/json\r\n
+Date: Tue, 14 May 2024 12:13:28 GMT\r\n
+Content-Length: 477
+", $request));
+
+ $response = $this->valet->getLatestVersion();
+
+ $this->assertSame('1.6.9', $response);
+ }
+
+ private function getFile(string $fileName): string|false
+ {
+ return file_get_contents(__DIR__ . '/MockResponse/' . $fileName);
+ }
+}
diff --git a/valet b/valet
index 9f213e7..977b2cf 100755
--- a/valet
+++ b/valet
@@ -3,7 +3,7 @@
set -e
SOURCE="${BASH_SOURCE[0]}"
-SUDO_COMMANDS="uninstall install start restart stop unsecure secure use isolate unisolate"
+SUDO_COMMANDS="uninstall install start restart stop unsecure secure use isolate unisolate proxy unproxy"
HOME_PATH=$HOME
function check_dependencies() {
@@ -21,9 +21,9 @@ function check_dependencies() {
done
if [[ $msg != '' ]]; then
- printf "${RED}You have missing Valet dependencies:\n"
- printf "$msg"
- printf "\nPlease refer to https://valetlinux.plus/ on how to install them.${NC}\n"
+ printf "%sYou have missing Valet dependencies:\n" "${RED}"
+ printf "%s" "$msg"
+ printf "\nPlease refer to https://valetlinux.plus/ on how to install them.%s\n" "${NC}"
exit 1
fi
}
@@ -33,9 +33,9 @@ function verify_ngrok_auth() {
then
if [[ -f "${HOME}/.ngrok2/ngrok.yml" ]]
then
- sudo -u $USER "$DIR/bin/ngrok" config upgrade --relocate
+ sudo -u "$USER" "$DIR/bin/ngrok" config upgrade --relocate
else
- printf "Please register for an ngrok account at: https://dashboard.ngrok.com/signup and install your authtoken.${NC}\n"
+ printf "Please register for an ngrok account at: https://dashboard.ngrok.com/signup and install your authtoken.%s\n" "${NC}"
printf "Valet can help you to install the authtoken, use 'valet ngrok-auth {authtoken}' command.\n"
exit 1
fi
@@ -47,7 +47,7 @@ function verify_ngrok_auth() {
# do it in pure Bash. So, we'll call into PHP CLI here to resolve.
if [[ -L $SOURCE ]]
then
- DIR=$(php -r "echo dirname(realpath('$SOURCE'));")
+ DIR=$( dirname "$( readlink -f "$SOURCE" )" )
else
DIR="$( cd "$( dirname "$SOURCE" )" && pwd )"
fi
@@ -57,7 +57,7 @@ fi
# Valet CLI script which is written in PHP. Will use PHP to do it.
if [[ ! -f "$DIR/cli/valet.php" ]]
then
- DIR=$(php -r "echo realpath('$DIR/../genesisweb/valet-linux-plus');")
+ DIR="$( cd "$DIR/../genesisweb/valet-linux-plus" && pwd )"
fi
# If the command is one of the commands that requires "sudo" privileges
@@ -91,11 +91,11 @@ then
verify_ngrok_auth
HOST="${PWD##*/}"
- DOMAIN=$(cat "$HOME/.valet/config.json" | jq -r ".domain")
- PORT=$(cat "$HOME/.valet/config.json" | jq -r ".port")
+ DOMAIN=$(cat "$HOME/.config/valet/config.json" | jq -r ".domain")
+ PORT=$(cat "$HOME/.config/valet/config.json" | jq -r ".port")
- for linkname in ~/.valet/Sites/*; do
- if [[ "$(readlink ${linkname})" = "$PWD" ]]
+ for linkname in ~/.config/valet/Sites/*; do
+ if [[ "$(readlink "${linkname}")" = "$PWD" ]]
then
HOST="${linkname##*/}"
fi
@@ -103,14 +103,14 @@ then
# Decide the correct PORT to use according if the site has a secure
# config or not.
- if grep --no-messages --quiet 443 ~/.valet/Nginx/$HOST*
+ if grep --no-messages --quiet 443 ~/.config/valet/Nginx/"$HOST"*
then
PORT=88
fi
# Fetch Ngrok URL In Background
bash "$DIR/cli/scripts/fetch-share-url.sh" &
- sudo -u $USER "$DIR/bin/ngrok" http "$HOST.$DOMAIN:$PORT" --host-header=rewrite
+ sudo -u "$USER" "$DIR/bin/ngrok" http "$HOST.$DOMAIN:$PORT" --host-header=rewrite
exit
# Proxy PHP commands to the "php" executable on the isolated site
@@ -118,9 +118,9 @@ elif [[ "$1" = "php" ]]
then
if [[ $2 == *"--site="* ]]; then
SITE=${2#*=}
- $(php "$DIR/cli/valet.php" which-php "$SITE") "${@:3}"
+ $(/usr/bin/php "$DIR/cli/valet.php" which-php "$SITE") "${@:3}"
else
- $(php "$DIR/cli/valet.php" which-php) "${@:2}"
+ $(/usr/bin/php "$DIR/cli/valet.php" which-php) "${@:2}"
fi
exit
@@ -131,10 +131,10 @@ then
if [[ $2 == *"--site="* ]]; then
SITE=${2#*=}
# shellcheck disable=SC2046
- $(php "$DIR/cli/valet.php" which-php "$SITE") $(which composer) "${@:3}"
+ $(/usr/bin/php "$DIR/cli/valet.php" which-php "$SITE") $(which composer) "${@:3}"
else
# shellcheck disable=SC2046
- $(php "$DIR/cli/valet.php" which-php) $(which composer) "${@:2}"
+ $(/usr/bin/php "$DIR/cli/valet.php" which-php) $(which composer) "${@:2}"
fi
exit
@@ -143,5 +143,5 @@ then
# and let it handle the request. These are commands which can be run
# without sudo and don't require taking over terminals like Ngrok.
else
- php "$DIR/cli/valet.php" "$@"
+ /usr/bin/php "$DIR/cli/valet.php" "$@"
fi