first commit

This commit is contained in:
Sampanna Rimal
2024-08-27 17:48:06 +05:45
commit 53c0140f58
10839 changed files with 1125847 additions and 0 deletions

View File

@ -0,0 +1,60 @@
<?php
namespace Laravel\Sail\Console;
use Illuminate\Console\Command;
use Laravel\Sail\Console\Concerns\InteractsWithDockerComposeServices;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'sail:add')]
class AddCommand extends Command
{
use InteractsWithDockerComposeServices;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sail:add
{services? : The services that should be added}
';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Add a service to an existing Sail installation';
/**
* Execute the console command.
*
* @return int|null
*/
public function handle()
{
if ($this->argument('services')) {
$services = $this->argument('services') == 'none' ? [] : explode(',', $this->argument('services'));
} elseif ($this->option('no-interaction')) {
$services = $this->defaultServices;
} else {
$services = $this->gatherServicesInteractively();
}
if ($invalidServices = array_diff($services, $this->services)) {
$this->components->error('Invalid services ['.implode(',', $invalidServices).'].');
return 1;
}
$this->buildDockerCompose($services);
$this->replaceEnvVariables($services);
$this->configurePhpUnit();
$this->prepareInstallation($services);
$this->output->writeln('');
$this->components->info('Additional Sail services installed successfully.');
}
}

View File

@ -0,0 +1,289 @@
<?php
namespace Laravel\Sail\Console\Concerns;
use Symfony\Component\Process\Process;
use Symfony\Component\Yaml\Yaml;
trait InteractsWithDockerComposeServices
{
/**
* The available services that may be installed.
*
* @var array<string>
*/
protected $services = [
'mysql',
'pgsql',
'mariadb',
'redis',
'memcached',
'meilisearch',
'typesense',
'minio',
'mailpit',
'selenium',
'soketi',
];
/**
* The default services used when the user chooses non-interactive mode.
*
* @var string[]
*/
protected $defaultServices = ['mysql', 'redis', 'selenium', 'mailpit'];
/**
* Gather the desired Sail services using an interactive prompt.
*
* @return array
*/
protected function gatherServicesInteractively()
{
if (function_exists('\Laravel\Prompts\multiselect')) {
return \Laravel\Prompts\multiselect(
label: 'Which services would you like to install?',
options: $this->services,
default: ['mysql'],
);
}
return $this->choice('Which services would you like to install?', $this->services, 0, null, true);
}
/**
* Build the Docker Compose file.
*
* @param array $services
* @return void
*/
protected function buildDockerCompose(array $services)
{
$composePath = base_path('docker-compose.yml');
$compose = file_exists($composePath)
? Yaml::parseFile($composePath)
: Yaml::parse(file_get_contents(__DIR__ . '/../../../stubs/docker-compose.stub'));
// Adds the new services as dependencies of the laravel.test service...
if (! array_key_exists('laravel.test', $compose['services'])) {
$this->warn('Couldn\'t find the laravel.test service. Make sure you add ['.implode(',', $services).'] to the depends_on config.');
} else {
$compose['services']['laravel.test']['depends_on'] = collect($compose['services']['laravel.test']['depends_on'] ?? [])
->merge($services)
->unique()
->values()
->all();
}
// Add the services to the docker-compose.yml...
collect($services)
->filter(function ($service) use ($compose) {
return ! array_key_exists($service, $compose['services'] ?? []);
})->each(function ($service) use (&$compose) {
$compose['services'][$service] = Yaml::parseFile(__DIR__ . "/../../../stubs/{$service}.stub")[$service];
});
// Merge volumes...
collect($services)
->filter(function ($service) {
return in_array($service, ['mysql', 'pgsql', 'mariadb', 'redis', 'meilisearch', 'typesense', 'minio']);
})->filter(function ($service) use ($compose) {
return ! array_key_exists($service, $compose['volumes'] ?? []);
})->each(function ($service) use (&$compose) {
$compose['volumes']["sail-{$service}"] = ['driver' => 'local'];
});
// If the list of volumes is empty, we can remove it...
if (empty($compose['volumes'])) {
unset($compose['volumes']);
}
// Replace Selenium with ARM base container on Apple Silicon...
if (in_array('selenium', $services) && in_array(php_uname('m'), ['arm64', 'aarch64'])) {
$compose['services']['selenium']['image'] = 'seleniarm/standalone-chromium';
}
file_put_contents($this->laravel->basePath('docker-compose.yml'), Yaml::dump($compose, Yaml::DUMP_OBJECT_AS_MAP));
}
/**
* Replace the Host environment variables in the app's .env file.
*
* @param array $services
* @return void
*/
protected function replaceEnvVariables(array $services)
{
$environment = file_get_contents($this->laravel->basePath('.env'));
if (in_array('mysql', $services) ||
in_array('mariadb', $services) ||
in_array('pgsql', $services)) {
$defaults = [
'# DB_HOST=127.0.0.1',
'# DB_PORT=3306',
'# DB_DATABASE=laravel',
'# DB_USERNAME=root',
'# DB_PASSWORD=',
];
foreach ($defaults as $default) {
$environment = str_replace($default, substr($default, 2), $environment);
}
}
if (in_array('mysql', $services)) {
$environment = preg_replace('/DB_CONNECTION=.*/', 'DB_CONNECTION=mysql', $environment);
$environment = str_replace('DB_HOST=127.0.0.1', "DB_HOST=mysql", $environment);
}elseif (in_array('pgsql', $services)) {
$environment = preg_replace('/DB_CONNECTION=.*/', 'DB_CONNECTION=pgsql', $environment);
$environment = str_replace('DB_HOST=127.0.0.1', "DB_HOST=pgsql", $environment);
$environment = str_replace('DB_PORT=3306', "DB_PORT=5432", $environment);
} elseif (in_array('mariadb', $services)) {
if ($this->laravel->config->has('database.connections.mariadb')) {
$environment = preg_replace('/DB_CONNECTION=.*/', 'DB_CONNECTION=mariadb', $environment);
}
$environment = str_replace('DB_HOST=127.0.0.1', "DB_HOST=mariadb", $environment);
}
$environment = str_replace('DB_USERNAME=root', "DB_USERNAME=sail", $environment);
$environment = preg_replace("/DB_PASSWORD=(.*)/", "DB_PASSWORD=password", $environment);
if (in_array('memcached', $services)) {
$environment = str_replace('MEMCACHED_HOST=127.0.0.1', 'MEMCACHED_HOST=memcached', $environment);
}
if (in_array('redis', $services)) {
$environment = str_replace('REDIS_HOST=127.0.0.1', 'REDIS_HOST=redis', $environment);
}
if (in_array('meilisearch', $services)) {
$environment .= "\nSCOUT_DRIVER=meilisearch";
$environment .= "\nMEILISEARCH_HOST=http://meilisearch:7700\n";
$environment .= "\nMEILISEARCH_NO_ANALYTICS=false\n";
}
if (in_array('typesense', $services)) {
$environment .= "\nSCOUT_DRIVER=typesense";
$environment .= "\nTYPESENSE_HOST=typesense";
$environment .= "\nTYPESENSE_PORT=8108";
$environment .= "\nTYPESENSE_PROTOCOL=http";
$environment .= "\nTYPESENSE_API_KEY=xyz\n";
}
if (in_array('soketi', $services)) {
$environment = preg_replace("/^BROADCAST_DRIVER=(.*)/m", "BROADCAST_DRIVER=pusher", $environment);
$environment = preg_replace("/^PUSHER_APP_ID=(.*)/m", "PUSHER_APP_ID=app-id", $environment);
$environment = preg_replace("/^PUSHER_APP_KEY=(.*)/m", "PUSHER_APP_KEY=app-key", $environment);
$environment = preg_replace("/^PUSHER_APP_SECRET=(.*)/m", "PUSHER_APP_SECRET=app-secret", $environment);
$environment = preg_replace("/^PUSHER_HOST=(.*)/m", "PUSHER_HOST=soketi", $environment);
$environment = preg_replace("/^PUSHER_PORT=(.*)/m", "PUSHER_PORT=6001", $environment);
$environment = preg_replace("/^PUSHER_SCHEME=(.*)/m", "PUSHER_SCHEME=http", $environment);
$environment = preg_replace("/^VITE_PUSHER_HOST=(.*)/m", "VITE_PUSHER_HOST=localhost", $environment);
}
if (in_array('mailpit', $services)) {
$environment = preg_replace("/^MAIL_MAILER=(.*)/m", "MAIL_MAILER=smtp", $environment);
$environment = preg_replace("/^MAIL_HOST=(.*)/m", "MAIL_HOST=mailpit", $environment);
$environment = preg_replace("/^MAIL_PORT=(.*)/m", "MAIL_PORT=1025", $environment);
}
file_put_contents($this->laravel->basePath('.env'), $environment);
}
/**
* Configure PHPUnit to use the dedicated testing database.
*
* @return void
*/
protected function configurePhpUnit()
{
if (! file_exists($path = $this->laravel->basePath('phpunit.xml'))) {
$path = $this->laravel->basePath('phpunit.xml.dist');
if (! file_exists($path)) {
return;
}
}
$phpunit = file_get_contents($path);
$phpunit = preg_replace('/^.*DB_CONNECTION.*\n/m', '', $phpunit);
$phpunit = str_replace('<!-- <env name="DB_DATABASE" value=":memory:"/> -->', '<env name="DB_DATABASE" value="testing"/>', $phpunit);
file_put_contents($this->laravel->basePath('phpunit.xml'), $phpunit);
}
/**
* Install the devcontainer.json configuration file.
*
* @return void
*/
protected function installDevContainer()
{
if (! is_dir($this->laravel->basePath('.devcontainer'))) {
mkdir($this->laravel->basePath('.devcontainer'), 0755, true);
}
file_put_contents(
$this->laravel->basePath('.devcontainer/devcontainer.json'),
file_get_contents(__DIR__.'/../../../stubs/devcontainer.stub')
);
$environment = file_get_contents($this->laravel->basePath('.env'));
$environment .= "\nWWWGROUP=1000";
$environment .= "\nWWWUSER=1000\n";
file_put_contents($this->laravel->basePath('.env'), $environment);
}
/**
* Prepare the installation by pulling and building any necessary images.
*
* @param array $services
* @return void
*/
protected function prepareInstallation($services)
{
// Ensure docker is installed...
if ($this->runCommands(['docker info > /dev/null 2>&1']) !== 0) {
return;
}
if (count($services) > 0) {
$this->runCommands([
'./vendor/bin/sail pull '.implode(' ', $services),
]);
}
$this->runCommands([
'./vendor/bin/sail build',
]);
}
/**
* Run the given commands.
*
* @param array $commands
* @return int
*/
protected function runCommands($commands)
{
$process = Process::fromShellCommandline(implode(' && ', $commands), null, null, null, null);
if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) {
try {
$process->setTty(true);
} catch (\RuntimeException $e) {
$this->output->writeln(' <bg=yellow;fg=black> WARN </> '.$e->getMessage().PHP_EOL);
}
}
return $process->run(function ($type, $line) {
$this->output->write(' '.$line);
});
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace Laravel\Sail\Console;
use Illuminate\Console\Command;
use RuntimeException;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Process\Process;
#[AsCommand(name: 'sail:install')]
class InstallCommand extends Command
{
use Concerns\InteractsWithDockerComposeServices;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sail:install
{--with= : The services that should be included in the installation}
{--devcontainer : Create a .devcontainer configuration directory}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Install Laravel Sail\'s default Docker Compose file';
/**
* Execute the console command.
*
* @return int|null
*/
public function handle()
{
if ($this->option('with')) {
$services = $this->option('with') == 'none' ? [] : explode(',', $this->option('with'));
} elseif ($this->option('no-interaction')) {
$services = $this->defaultServices;
} else {
$services = $this->gatherServicesInteractively();
}
if ($invalidServices = array_diff($services, $this->services)) {
$this->components->error('Invalid services ['.implode(',', $invalidServices).'].');
return 1;
}
$this->buildDockerCompose($services);
$this->replaceEnvVariables($services);
$this->configurePhpUnit();
if ($this->option('devcontainer')) {
$this->installDevContainer();
}
$this->prepareInstallation($services);
$this->output->writeln('');
$this->components->info('Sail scaffolding installed successfully. You may run your Docker containers using Sail\'s "up" command.');
$this->output->writeln('<fg=gray>➜</> <options=bold>./vendor/bin/sail up</>');
if (in_array('mysql', $services) ||
in_array('mariadb', $services) ||
in_array('pgsql', $services)) {
$this->components->warn('A database service was installed. Run "artisan migrate" to prepare your database:');
$this->output->writeln('<fg=gray>➜</> <options=bold>./vendor/bin/sail artisan migrate</>');
}
$this->output->writeln('');
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Laravel\Sail\Console;
use Illuminate\Console\Command;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'sail:publish')]
class PublishCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sail:publish';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Publish the Laravel Sail Docker files';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$this->call('vendor:publish', ['--tag' => 'sail-docker']);
$this->call('vendor:publish', ['--tag' => 'sail-database']);
file_put_contents(
$this->laravel->basePath('docker-compose.yml'),
str_replace(
[
'./vendor/laravel/sail/runtimes/8.3',
'./vendor/laravel/sail/runtimes/8.2',
'./vendor/laravel/sail/runtimes/8.1',
'./vendor/laravel/sail/runtimes/8.0',
'./vendor/laravel/sail/database/mysql',
'./vendor/laravel/sail/database/pgsql'
],
[
'./docker/8.3',
'./docker/8.2',
'./docker/8.1',
'./docker/8.0',
'./docker/mysql',
'./docker/pgsql'
],
file_get_contents($this->laravel->basePath('docker-compose.yml'))
)
);
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace Laravel\Sail;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
use Laravel\Sail\Console\AddCommand;
use Laravel\Sail\Console\InstallCommand;
use Laravel\Sail\Console\PublishCommand;
class SailServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$this->registerCommands();
$this->configurePublishing();
}
/**
* Register the console commands for the package.
*
* @return void
*/
protected function registerCommands()
{
if ($this->app->runningInConsole()) {
$this->commands([
InstallCommand::class,
AddCommand::class,
PublishCommand::class,
]);
}
}
/**
* Configure publishing for the package.
*
* @return void
*/
protected function configurePublishing()
{
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__ . '/../runtimes' => $this->app->basePath('docker'),
], ['sail', 'sail-docker']);
$this->publishes([
__DIR__ . '/../bin/sail' => $this->app->basePath('sail'),
], ['sail', 'sail-bin']);
$this->publishes([
__DIR__ . '/../database' => $this->app->basePath('docker'),
], ['sail', 'sail-database']);
}
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [
InstallCommand::class,
PublishCommand::class,
];
}
}