diff --git a/.gitignore b/.gitignore index 8b7ef350..7eb44a38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +/.idea +/.vscode /vendor composer.lock diff --git a/src/Console/AddCommand.php b/src/Console/AddCommand.php index 9cab9184..98464cde 100644 --- a/src/Console/AddCommand.php +++ b/src/Console/AddCommand.php @@ -3,6 +3,7 @@ namespace Laravel\Sail\Console; use Illuminate\Console\Command; +use Laravel\Sail\Sail; use Laravel\Sail\Console\Concerns\InteractsWithDockerComposeServices; use Symfony\Component\Console\Attribute\AsCommand; @@ -34,15 +35,17 @@ class AddCommand extends Command */ public function handle() { + $availableServices = Sail::availableServices(); + if ($this->argument('services')) { $services = $this->argument('services') == 'none' ? [] : explode(',', $this->argument('services')); } elseif ($this->option('no-interaction')) { - $services = $this->defaultServices; + $services = Sail::availableServices(true); } else { $services = $this->gatherServicesInteractively(); } - if ($invalidServices = array_diff($services, $this->services)) { + if ($invalidServices = array_diff($services, $availableServices)) { $this->components->error('Invalid services ['.implode(',', $invalidServices).'].'); return 1; @@ -52,6 +55,8 @@ public function handle() $this->replaceEnvVariables($services); $this->configurePhpUnit(); + Sail::runPreInstallCallbacks($this, $services); + $this->prepareInstallation($services); $this->output->writeln(''); diff --git a/src/Console/Concerns/InteractsWithDockerComposeServices.php b/src/Console/Concerns/InteractsWithDockerComposeServices.php index 8299e853..428e1345 100644 --- a/src/Console/Concerns/InteractsWithDockerComposeServices.php +++ b/src/Console/Concerns/InteractsWithDockerComposeServices.php @@ -2,39 +2,12 @@ namespace Laravel\Sail\Console\Concerns; +use Laravel\Sail\Sail; use Symfony\Component\Process\Process; use Symfony\Component\Yaml\Yaml; trait InteractsWithDockerComposeServices { - /** - * The available services that may be installed. - * - * @var array - */ - protected $services = [ - 'mysql', - 'pgsql', - 'mariadb', - 'mongodb', - 'redis', - 'valkey', - '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. * @@ -42,15 +15,17 @@ trait InteractsWithDockerComposeServices */ protected function gatherServicesInteractively() { + $services = Sail::availableServices(); + if (function_exists('\Laravel\Prompts\multiselect')) { return \Laravel\Prompts\multiselect( label: 'Which services would you like to install?', - options: $this->services, + options: $services, default: ['mysql'], ); } - return $this->choice('Which services would you like to install?', $this->services, 0, null, true); + return $this->choice('Which services would you like to install?', $services, 0, null, true); } /** @@ -62,22 +37,38 @@ protected function gatherServicesInteractively() protected function buildDockerCompose(array $services) { $composePath = base_path('docker-compose.yml'); + $appService = 'laravel.test'; - $compose = file_exists($composePath) - ? Yaml::parseFile($composePath) - : Yaml::parse(file_get_contents(__DIR__ . '/../../../stubs/docker-compose.stub')); + if (file_exists($composePath)) { + $compose = Yaml::parseFile($composePath); + } else { + $template = str_replace( + '{{APP}}', + $appService, + file_get_contents(Sail::baseTemplate()), + $count + ); + if ($count === 0) { + $this->error('Missing app service in the base template. Make sure you have it with the {{APP}} placeholder.'); + exit(1); + } + $compose = Yaml::parse($template); + } // Prepare the installation of the "mariadb-client" package if the MariaDB service is used... if (in_array('mariadb', $services)) { - $compose['services']['laravel.test']['build']['args']['MYSQL_CLIENT'] = 'mariadb-client'; + $compose['services'][$appService]['build']['args']['MYSQL_CLIENT'] = 'mariadb-client'; } - // 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.'); + // Adds the new services as dependencies of the app service... + $dependencies = collect($services)->filter(function ($service) { + return Sail::isDependency($service); + })->toArray(); + if (! array_key_exists($appService, $compose['services'])) { + $this->warn('Couldn\'t find the '.$appService.' service. Make sure you add ['.implode(',', $dependencies).'] to the depends_on config.'); } else { - $compose['services']['laravel.test']['depends_on'] = collect($compose['services']['laravel.test']['depends_on'] ?? []) - ->merge($services) + $compose['services'][$appService]['depends_on'] = collect($compose['services'][$appService]['depends_on'] ?? []) + ->merge($dependencies) ->unique() ->values() ->all(); @@ -88,13 +79,31 @@ protected function buildDockerCompose(array $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]; + $stubPath = Sail::stub($service); + if (file_exists($stubPath)) { + $compose['services'][$service] = Yaml::parseFile($stubPath)[$service]; + } else { + $this->warn("No stub found for service [{$service}]. Skipping."); + } }); + // Merge networks + $compose['networks'] = collect(Sail::networks())->merge($compose['networks'] ?? [])->toArray(); + + foreach ($compose['networks'] as $name => $network) { + if ($network['external'] ?? false) { + exec("docker network ls --filter name=^" . escapeshellarg($name) . "$ -q", $check); + if (empty($check)) { + exec("docker network create ".escapeshellarg($name), $output); + $this->components->info("$name network has been created."); + } + } + } + // Merge volumes... collect($services) ->filter(function ($service) { - return in_array($service, ['mysql', 'pgsql', 'mariadb', 'mongodb', 'redis', 'valkey', 'meilisearch', 'typesense', 'minio']); + return Sail::isPersistent($service); })->filter(function ($service) use ($compose) { return ! array_key_exists($service, $compose['volumes'] ?? []); })->each(function ($service) use (&$compose) { @@ -123,87 +132,7 @@ 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('valkey',$services)){ - $environment = str_replace('REDIS_HOST=127.0.0.1', 'REDIS_HOST=valkey', $environment); - } - - if (in_array('mongodb', $services)) { - $environment .= "\nMONGODB_URI=mongodb://mongodb:27017"; - $environment .= "\nMONGODB_DATABASE=laravel"; - } - - 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); - } + $environment = Sail::configureEnv($environment, $services); file_put_contents($this->laravel->basePath('.env'), $environment); } @@ -244,7 +173,11 @@ protected function installDevContainer() file_put_contents( $this->laravel->basePath('.devcontainer/devcontainer.json'), - file_get_contents(__DIR__.'/../../../stubs/devcontainer.stub') + str_replace( + '{{APP}}', + 'laravel.test', + file_get_contents(__DIR__.'/../../../stubs/devcontainer.stub') ?: '' + ) ); $environment = file_get_contents($this->laravel->basePath('.env')); diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 7d21a03f..65a5eef1 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -3,14 +3,14 @@ namespace Laravel\Sail\Console; use Illuminate\Console\Command; -use RuntimeException; +use Laravel\Sail\Sail; +use Laravel\Sail\Console\Concerns\InteractsWithDockerComposeServices; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Process\Process; #[AsCommand(name: 'sail:install')] class InstallCommand extends Command { - use Concerns\InteractsWithDockerComposeServices; + use InteractsWithDockerComposeServices; /** * The name and signature of the console command. @@ -36,15 +36,17 @@ class InstallCommand extends Command */ public function handle() { + $availableServices = Sail::availableServices(); + if ($this->option('with')) { $services = $this->option('with') == 'none' ? [] : explode(',', $this->option('with')); } elseif ($this->option('no-interaction')) { - $services = $this->defaultServices; + $services = Sail::availableServices(true); } else { $services = $this->gatherServicesInteractively(); } - if ($invalidServices = array_diff($services, $this->services)) { + if ($invalidServices = array_diff($services, $availableServices)) { $this->components->error('Invalid services ['.implode(',', $invalidServices).'].'); return 1; @@ -58,6 +60,8 @@ public function handle() $this->installDevContainer(); } + Sail::runPreInstallCallbacks($this, $services); + $this->prepareInstallation($services); $this->output->writeln(''); diff --git a/src/Console/PublishCommand.php b/src/Console/PublishCommand.php index 4ea7551d..cdae0eb6 100644 --- a/src/Console/PublishCommand.php +++ b/src/Console/PublishCommand.php @@ -3,6 +3,7 @@ namespace Laravel\Sail\Console; use Illuminate\Console\Command; +use Laravel\Sail\Sail; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'sail:publish')] @@ -56,5 +57,7 @@ public function handle() file_get_contents($this->laravel->basePath('docker-compose.yml')) ) ); + + Sail::runPostPublishCallbacks($this); } } diff --git a/src/Sail.php b/src/Sail.php new file mode 100644 index 00000000..2d29a37c --- /dev/null +++ b/src/Sail.php @@ -0,0 +1,35 @@ +|Closure|null, callback: ?Closure}> + */ + protected array $services = [ + 'mysql' => [ + 'persistent' => true, + 'default' => true, + ], + 'pgsql' => [ + 'persistent' => true, + ], + 'mariadb' => [ + 'persistent' => true, + ], + 'mongodb' => [ + 'persistent' => true, + 'env' => [ + 'MONGODB_URI' => 'mongodb://mongodb:27017', + 'MONGODB_DATABASE' => 'laravel', + ], + ], + 'redis' => [ + 'persistent' => true, + 'default' => true, + 'env' => [ + 'REDIS_HOST' => 'redis', + ], + ], + 'valkey' => [ + 'persistent' => true, + 'env' => [ + 'REDIS_HOST' => 'valkey', + ], + ], + 'memcached' => [ + 'env' => [ + 'MEMCACHED_HOST' => 'memcached', + ], + ], + 'meilisearch' => [ + 'persistent' => true, + 'env' => [ + 'SCOUT_DRIVER' => 'meilisearch', + 'MEILISEARCH_HOST' => 'http://meilisearch:7700', + 'MEILISEARCH_NO_ANALYTICS' => 'false', + ], + ], + 'typesense' => [ + 'persistent' => true, + 'env' => [ + 'SCOUT_DRIVER' => 'typesense', + 'TYPESENSE_HOST' => 'typesense', + 'TYPESENSE_PORT' => '8108', + 'TYPESENSE_PROTOCOL' => 'http', + 'TYPESENSE_API_KEY' => 'xyz', + ], + ], + 'minio' => [ + 'persistent' => true, + ], + 'mailpit' => [ + 'default' => true, + 'env' => [ + 'MAIL_MAILER' => 'smtp', + 'MAIL_HOST' => 'mailpit', + 'MAIL_PORT' => '1025', + ], + ], + 'selenium' => [ + 'default' => true, + ], + 'soketi' => [ + 'env' => [ + 'BROADCAST_DRIVER' => 'pusher', + 'PUSHER_APP_ID' => 'app-id', + 'PUSHER_APP_KEY' => 'app-key', + 'PUSHER_APP_SECRET' => 'app-secret', + 'PUSHER_HOST' => 'soketi', + 'PUSHER_PORT' => '6001', + 'PUSHER_SCHEME' => 'http', + 'VITE_PUSHER_HOST' => 'localhost', + ], + ], + ]; + + /** + * The networks services communicating on + * + * @var array> + */ + protected array $networks = [ + 'sail' => [ + 'driver' => 'bridge', + ] + ]; + + /** + * Path to the base docker compose template + * + * @var string + */ + protected string $composeStub = __DIR__ . '/../stubs/docker-compose.stub'; + + /** + * Callbacks to be run after all services are configured + * + * @var Closure[] + */ + protected array $preInstallCallbacks = []; + + /** + * Callbacks to be run during the publish command + * + * @var Closure[] + */ + protected array $postPublishCallbacks = []; + + public function __construct() + { + $uncommentDbVars = function (string $environment): string { + $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); + } + return $environment; + }; + + $setDbCredentials = function (string $environment): string { + $environment = str_replace('DB_USERNAME=root', "DB_USERNAME=sail", $environment); + return preg_replace("/DB_PASSWORD=(.*)/", "DB_PASSWORD=password", $environment); + }; + + $this->services['mysql']['env'] = function (string $environment) use ($uncommentDbVars, $setDbCredentials): string { + $environment = $uncommentDbVars($environment); + $environment = preg_replace('/DB_CONNECTION=.*/', 'DB_CONNECTION=mysql', $environment); + $environment = str_replace('DB_HOST=127.0.0.1', "DB_HOST=mysql", $environment); + return $setDbCredentials($environment); + }; + + $this->services['pgsql']['env'] = function (string $environment) use ($uncommentDbVars, $setDbCredentials): string { + $environment = $uncommentDbVars($environment); + $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); + return $setDbCredentials($environment); + }; + + $this->services['mariadb']['env'] = function (string $environment) use ($uncommentDbVars, $setDbCredentials): string { + $environment = $uncommentDbVars($environment); + if (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); + return $setDbCredentials($environment); + }; + } + + /** + * Set the base Docker Compose template containing a php service named as '{{APP}}' + * + * @param string $stub Path to the base Docker Compose stub + * @return $this + */ + public function setBaseTemplate(string $stub): self + { + $this->composeStub = $stub; + + return $this; + } + + /** + * Get the path to the base Docker Compose template + * + * @return string + */ + public function baseTemplate(): string + { + return $this->composeStub; + } + + /** + * Register a new service with its Docker Compose stub. + * + * @param string $service + * @param string|null $stub + * @param bool|null $isPersistent + * @param bool|null $isDefault + * @param bool|null $isDependency + * @param array|Closure|null $env + * @param Closure|null $preInstallCallback + * @return self + */ + public function addService(string $service, + ?string $stub = null, + ?bool $isPersistent = null, + ?bool $isDefault = null, + ?bool $isDependency = null, + array|Closure|null $env = null, + ?Closure $preInstallCallback = null): self + { + $this->services[$service] = [ + 'stub' => $stub ?? $this->services[$service]['stub'] ?? null, + 'persistent' => $isPersistent ?? $this->services[$service]['persistent'] ?? null, + 'default' => $isDefault ?? $this->services[$service]['default'] ?? null, + 'dependency' => $isDependency ?? $this->services[$service]['dependency'] ?? null, + 'env' => $env ?? $this->services[$service]['env'] ?? null, + 'callback' => $preInstallCallback ?? $this->services[$service]['callback'] ?? null, + ]; + + return $this; + } + + /** + * @param array $network + * @return $this + */ + public function addNetwork(array $network): self + { + $this->networks = array_merge($this->networks, $network); + + return $this; + } + + /** + * Add a callback to the pipeline executed during the installation command. + * + * @param Closure $callback + * @return $this + */ + public function addPreInstallCallback(Closure $callback): self + { + $this->preInstallCallbacks[] = $callback; + + return $this; + } + + /** + * Add a callback to the pipeline executed during the publish command. + * + * @param Closure $callback + * @return $this + */ + public function addPostPublishCallback(Closure $callback): self + { + $this->postPublishCallbacks[] = $callback; + + return $this; + } + + /** + * Get all available services, including defaults. + * + * @param bool $isDefault If true, returns only default services + * @return array + */ + public function availableServices(bool $isDefault = false): array + { + $services = []; + foreach ($this->services as $key => $value) { + $services[] = is_string($value) ? $value : $key; + } + + if ($isDefault) { + $defaults = []; + foreach ($this->services as $key => $value) { + if (is_array($value) && ($value['default'] ?? false)) { + $defaults[] = $key; + } + } + return $defaults; + } + + return $services; + } + + /** + * Get the list of networks defined for the docker-compose file + * + * @return array + */ + public function networks(): array + { + return $this->networks; + } + + /** + * Get the stub path for a given service. + * + * @param string $service + * @return string + */ + public function stub(string $service): string + { + return $this->services[$service]['stub'] ?? __DIR__ . '/../stubs/'.$service.'.stub'; + } + + /** + * Check if a service requires a persistent volume. + * + * @param string $service + * @return bool + */ + public function isPersistent(string $service): bool + { + return $this->services[$service]['persistent'] ?? false; + } + + /** + * Check if a service is required by {{APP}} service + * + * @param string $service + * @return bool + */ + public function isDependency(string $service): bool + { + return $this->services[$service]['dependency'] ?? true; + } + + /** + * Add or replace environment variables. + * + * @param string $environment + * @param array $services + * @return string + */ + public function configureEnv(string $environment, array $services): string + { + foreach ($services as $service) { + if (!isset($this->services[$service]) || !is_array($this->services[$service])) { + continue; + } + + $envConfig = $this->services[$service]['env'] ?? null; + + if ($envConfig instanceof Closure) { + $environment = $envConfig($environment); + continue; + } + + if (!is_array($envConfig)) { + continue; + } + + $lines = Str::of($environment)->rtrim()->explode("\n")->all(); + $newGroups = []; + + foreach ($envConfig as $key => $value) { + $line = "$key=$value"; + $prefix = strtok($key, '_'); + + if (Str::contains($environment, "$key=")) { + $lines = collect($lines)->map(fn($l) => Str::startsWith($l, "$key=") ? $line : $l)->all(); + } elseif (collect($lines)->first(fn($l) => Str::startsWith($l, "$prefix"))) { + $pos = collect($lines)->search(fn($l) => Str::startsWith($l, "$prefix")) + 1; + array_splice($lines, $pos, 0, $line); + } else { + $newGroups[$prefix][] = $line; + } + } + + $environment = implode("\n", $lines); + if ($newGroups) { + $environment .= "\n\n" . collect($newGroups)->flatten()->implode("\n") . "\n"; + } + } + + return rtrim($environment); + } + + /** + * Execute callbacks set for the installation command. + * + * @param Command $command + * @param array $services + * @param string $appService + * @return $this + */ + public function runPreInstallCallbacks(Command $command, array $services, string $appService = 'laravel.test'): self + { + foreach ($services as $service) { + if (isset($this->services[$service]) && is_array($this->services[$service]) && ($this->services[$service]['callback'] ?? null) !== null) { + $this->services[$service]['callback']($command, $services, $appService); + } + } + foreach ($this->preInstallCallbacks as $callback) { + $callback($command, $services, $appService); + } + + return $this; + } + + /** + * Execute callbacks set for the publish command + * + * @param Command $command + * @return $this + */ + public function runPostPublishCallbacks(Command $command): self + { + foreach ($this->postPublishCallbacks as $callback) { + $callback($command); + } + + return $this; + } +} diff --git a/stubs/devcontainer.stub b/stubs/devcontainer.stub index b96384fb..20d6df88 100644 --- a/stubs/devcontainer.stub +++ b/stubs/devcontainer.stub @@ -4,7 +4,7 @@ "dockerComposeFile": [ "../docker-compose.yml" ], - "service": "laravel.test", + "service": "{{APP}}", "workspaceFolder": "/var/www/html", "customizations": { "vscode": { diff --git a/stubs/docker-compose.stub b/stubs/docker-compose.stub index c574efcb..7c752fa2 100644 --- a/stubs/docker-compose.stub +++ b/stubs/docker-compose.stub @@ -1,6 +1,6 @@ # For more information: https://laravel.com/docs/sail services: - laravel.test: + {{APP}}: build: context: ./vendor/laravel/sail/runtimes/{{PHP_VERSION}} dockerfile: Dockerfile @@ -22,6 +22,3 @@ services: - '.:/var/www/html' networks: - sail -networks: - sail: - driver: bridge