loom-spinner-cli/src/Command/SpinCommand.php

217 lines
6.5 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace Loom\Spinner\Command;
2025-04-13 13:11:35 +01:00
use Loom\Spinner\Classes\File\DockerComposeFileBuilder;
2025-04-13 20:07:48 +01:00
use Loom\Spinner\Classes\File\NginxDockerFileBuilder;
use Loom\Spinner\Classes\File\PHPDockerFileBuilder;
use Loom\Spinner\Classes\File\SpinnerFilePath;
2025-04-12 19:09:40 +01:00
use Loom\Spinner\Classes\OS\PortGenerator;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(name: 'spin:up', description: 'Spin up a new development environment')]
class SpinCommand extends AbstractSpinnerCommand
{
2025-04-12 19:09:40 +01:00
private PortGenerator $portGenerator;
private array $ports;
public function __construct()
{
$this->portGenerator = new PortGenerator();
$this->ports = [
'php' => $this->portGenerator->generateRandomPort(),
2025-04-13 20:07:48 +01:00
'nginx' => $this->portGenerator->generateRandomPort(),
2025-04-12 19:09:40 +01:00
];
parent::__construct();
}
/**
* @throws \Exception
*/
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'The name for your Docker container.')
->addArgument('path', InputArgument::REQUIRED, 'The absolute path to your projects root directory.')
->addOption(
'php',
null,
InputOption::VALUE_OPTIONAL,
'The PHP version to use (e.g., 8.0).'
)
->addOption(
2025-04-13 20:07:48 +01:00
'disable-node',
null,
InputOption::VALUE_NONE,
'Set this flag to disable Node.js for your environment.'
)
->addOption(
'disable-server',
null,
InputOption::VALUE_NONE,
'Set this flag to not include a web server for your environment.'
)
->addOption(
'disable-xdebug',
null,
InputOption::VALUE_NONE,
'Set this flag to disable XDebug for your environment.'
)
->addOption('node', null, InputOption::VALUE_OPTIONAL, 'The Node.js version to use (e.g. 20).');
}
/**
* @throws \Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (parent::execute($input, $output)) {
return Command::FAILURE;
}
if ($this->projectDataExists()) {
return Command::SUCCESS;
}
$this->style->success("Spinning up a new development environment...");
$this->style->text('Creating project data...');
2025-04-12 19:09:40 +01:00
$this->createProjectData($input);
$this->style->success('Project data created.');
2025-04-12 19:09:40 +01:00
$this->style->text('Building Docker images...');
2025-04-13 20:07:48 +01:00
$command = $this->buildDockerComposeCommand(sprintf('-p %s up', $input->getArgument('name')));
passthru($command);
2025-04-12 19:09:40 +01:00
return Command::SUCCESS;
}
protected function projectDataExists(): bool
2025-04-12 19:09:40 +01:00
{
if ($this->config->getFilePaths()->get('projectData')->exists()) {
2025-04-14 02:52:39 +01:00
$this->style->warning('Project already exists. Skipping new build.');
return true;
}
return false;
}
/**
* @throws \Exception
*/
private function createProjectData(InputInterface $input): void
{
$this->createProjectDataDirectory();
2025-04-12 19:09:40 +01:00
$this->createEnvironmentFile($input);
2025-04-13 13:11:35 +01:00
$this->buildDockerComposeFile($input);
2025-04-13 20:07:48 +01:00
$this->buildDockerfiles($input);
}
/**
* @throws \Exception
*/
private function createProjectDataDirectory(): void
{
$projectData = $this->config->getFilePaths()->get('projectData');
if (!$projectData instanceof SpinnerFilePath) {
throw new \Exception('Invalid project data directory provided.');
}
mkdir($projectData->getProvidedPath(), 0777, true);
2025-04-12 19:09:40 +01:00
}
/**
* @throws \Exception
*/
2025-04-12 19:09:40 +01:00
private function createEnvironmentFile(InputInterface $input): void
{
$projectEnv = $this->config->getFilePaths()->get('projectEnv');
if (!$projectEnv instanceof SpinnerFilePath) {
throw new \Exception('Invalid project environment file provided.');
}
file_put_contents(
$projectEnv->getProvidedPath(),
sprintf(
file_get_contents($this->config->getFilePaths()->get('envTemplate')->getAbsolutePath()),
$this->config->getFilePaths()->get('project')->getAbsolutePath(),
$input->getArgument('name'),
2025-04-13 13:35:39 +01:00
$this->config->getPhpVersion($input),
2025-04-12 19:09:40 +01:00
$this->getPort('php'),
2025-04-13 20:07:48 +01:00
$this->getPort('nginx'),
)
);
2025-04-12 19:09:40 +01:00
}
/**
* @throws \Exception
*/
2025-04-13 13:11:35 +01:00
private function buildDockerComposeFile(InputInterface $input): void
{
$this->createProjectPhpFpmDirectory();
2025-04-13 20:07:48 +01:00
if ($this->config->isServerEnabled($input)) {
$this->createProjectNginxDirectory();
(new NginxDockerFileBuilder($this->config))->build($input)->save();
}
2025-04-13 13:11:35 +01:00
(new DockerComposeFileBuilder($this->config))->build($input)->save();
}
2025-04-13 20:07:48 +01:00
/**
* @throws \Exception
*/
private function buildDockerfiles(InputInterface $input): void
{
(new PHPDockerFileBuilder($this->config))->build($input)->save();
}
2025-04-13 13:11:35 +01:00
/**
* @throws \Exception
*/
private function createProjectPhpFpmDirectory(): void
2025-04-12 19:09:40 +01:00
{
$projectData = $this->config->getFilePaths()->get('projectData');
2025-04-13 13:11:35 +01:00
if (!$projectData instanceof SpinnerFilePath) {
throw new \Exception('Invalid project data directory provided.');
}
if (!file_exists($projectData->getProvidedPath() . '/php-fpm')) {
mkdir($projectData->getProvidedPath() . '/php-fpm', 0777, true);
2025-04-12 19:09:40 +01:00
}
}
/**
* @throws \Exception
*/
2025-04-13 20:07:48 +01:00
private function createProjectNginxDirectory(): void
{
2025-04-13 20:07:48 +01:00
$projectData = $this->config->getFilePaths()->get('projectData');
if (!$projectData instanceof SpinnerFilePath) {
throw new \Exception('Invalid project data directory provided.');
}
if (!file_exists($projectData->getProvidedPath() . '/nginx')) {
mkdir($projectData->getProvidedPath() . '/nginx', 0777, true);
}
2025-04-12 19:09:40 +01:00
}
2025-04-13 13:35:39 +01:00
private function getPort(string $service): ?int
2025-04-12 19:09:40 +01:00
{
2025-04-13 13:35:39 +01:00
return $this->ports[$service];
}
}