Create basic project data structure from env template

This commit is contained in:
Daniel Winning 2025-04-12 17:28:57 +01:00
parent 940fbc1234
commit b8ad06cda3
11 changed files with 179 additions and 46 deletions

View file

@ -13,7 +13,8 @@
"require": {
"loomlabs/utility.collection": "^1.0",
"loomlabs/utility.filepath": "^1.1",
"symfony/console": "^7.2"
"symfony/console": "^7.2",
"symfony/yaml": "^7.2"
},
"require-dev": {
"phpunit/phpunit": "^12.1"

3
config/.template.env Normal file
View file

@ -0,0 +1,3 @@
PROJECT_DIRECTORY=%s
PROJECT_NAME=%s
PHP_VERSION=%s

View file

@ -0,0 +1,26 @@
services:
# nginx:
# build:
# context: ./nginx
# ports:
# - ${NGINX_PORT}:80
# volumes:
# - ${PROJECT_DIRECTORY}:/var/www/html:cached
# - ./nginx/conf.d:/etc/nginx/conf.d
# database:
# image: mysql:8.0
# command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
# ports:
# - ${MYSQL_PORT}:3306
# expose:
# - "3306"
# environment:
# MYSQL_ROOT_PASSWORD: docker
# volumes:
# - ./data/${PROJECT_NAME}/mysql:/var/lib/mysql:cached
# cache:
# image: redis:latest
# ports:
# - ${REDIS_PORT}:6379
# volumes:
# - ./data/${PROJECT_NAME}/redis:/data:cached

View file

@ -1,38 +0,0 @@
version: "3.9"
services:
nginx:
build:
context: ./nginx
ports:
- ${NGINX_PORT}:80
volumes:
- ${PROJECT_DIRECTORY}:/var/www/html:cached
- ./nginx/conf.d:/etc/nginx/conf.d
php:
build:
context: ./php-fpm
working_dir: /var/www/html
extra_hosts:
- host.docker.internal:host-gateway
volumes:
- ${PROJECT_DIRECTORY}:/var/www/html:cached
ports:
- ${PHP_PORT}:9003
database:
image: mysql:8.0
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
ports:
- ${MYSQL_PORT}:3306
expose:
- "3306"
environment:
MYSQL_ROOT_PASSWORD: docker
volumes:
- ./data/${PROJECT_NAME}/mysql:/var/lib/mysql:cached
cache:
image: redis:latest
ports:
- ${REDIS_PORT}:6379
volumes:
- ./data/${PROJECT_NAME}/redis:/data:cached

41
config/php-fpm/Dockerfile Normal file
View file

@ -0,0 +1,41 @@
ARG PHP_VERSION
ENV PHP_VERSION ${PHP_VERSION}
FROM php:${PHP_VERSION}-fpm
RUN mkdir -p /var/www/html
RUN apt-get -qq update && apt-get -qq install -y \
apt-transport-https \
git \
curl \
zip \
unzip \
libicu-dev \
imagick \
nano \
bash \
dnsutils
ENV NVM_DIR /root/.nvm
ENV NODE_VERSION 20
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash \
&& . "$NVM_DIR/nvm.sh" \
&& nvm install $NODE_VERSION \
&& nvm use $NODE_VERSION
COPY --from=node:20 /usr/local/bin/npx /usr/local/bin/npx
RUN docker-php-ext-configure intl > /dev/null
RUN docker-php-ext-install mysqli pdo pdo_mysql sockets intl exif bcmath opcache > /dev/null
RUN pecl install xdebug redis \
&& docker-php-ext-enable redis
COPY ./xdebug.ini.tmp "${PHP_INI_DIR}/conf.d/xdebug.ini"
COPY ./opcache.ini "${PHP_INI_DIR}/conf.d"
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
WORKDIR /var/www/html

10
config/php.yaml Normal file
View file

@ -0,0 +1,10 @@
php:
build:
context: ./php-fpm
working_dir: /var/www/html
extra_hosts:
- host.docker.internal:host-gateway
volumes:
- ${PROJECT_DIRECTORY}:/var/www/html:cached
ports:
- ${PHP_PORT}:9003

4
config/spinner.yaml Normal file
View file

@ -0,0 +1,4 @@
options:
environment:
php:
version: 8.4

2
data/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

View file

@ -8,8 +8,13 @@ use Loom\Utility\FilePath\FilePath;
class SpinnerFilePath extends FilePath
{
public function __construct(string $projectPath)
public function __construct(string $path)
{
parent::__construct(sprintf('%s%s%s', dirname(__DIR__, 3), DIRECTORY_SEPARATOR, $projectPath));
parent::__construct(sprintf('%s%s%s', dirname(__DIR__, 3), DIRECTORY_SEPARATOR, $path));
}
public function getProvidedPath(): string
{
return $this->path;
}
}

View file

@ -8,11 +8,11 @@ use Loom\Spinner\Classes\Collection\FilePathCollection;
use Loom\Spinner\Classes\File\SpinnerFilePath;
use Loom\Spinner\Classes\OS\System;
use Loom\Spinner\Command\Interface\ConsoleCommandInterface;
use Loom\Utility\FilePath\FilePath;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Yaml\Yaml;
class AbstractSpinnerCommand extends Command implements ConsoleCommandInterface
{
@ -34,6 +34,8 @@ class AbstractSpinnerCommand extends Command implements ConsoleCommandInterface
{
$this->setStyle($input, $output);
$this->style->title('Loom Spinner');
if (!$this->system->isDockerEngineRunning()) {
$this->style->error('It looks like the Docker Engine is not running. Please start it and try again.');
@ -41,7 +43,7 @@ class AbstractSpinnerCommand extends Command implements ConsoleCommandInterface
}
if ($input->hasArgument('path')) {
$projectDirectory = new FilePath($input->getArgument('path'));
$projectDirectory = new SpinnerFilePath($input->getArgument('path'));
if (!$projectDirectory->exists() || !$projectDirectory->isDirectory()) {
$this->style->error('The provided path is not a valid directory.');
@ -52,9 +54,58 @@ class AbstractSpinnerCommand extends Command implements ConsoleCommandInterface
$this->filePaths->add($projectDirectory, 'project');
}
if ($input->hasArgument('name')) {
$this->filePaths->add(
new SpinnerFilePath(sprintf('data/environments/%s', $input->getArgument('name'))),
'projectData'
);
$this->filePaths->add(
new SpinnerFilePath(sprintf('data/environments/%s/.env', $input->getArgument('name'))),
'projectEnv'
);
}
return Command::SUCCESS;
}
/**
* @throws \Exception
*/
protected function getDefaultConfig()
{
if (!$this->filePaths->get('defaultSpinnerConfig')?->exists()) {
throw new \Exception('Default spinner configuration file not found.');
}
return Yaml::parseFile($this->filePaths->get('defaultSpinnerConfig')->getAbsolutePath())['options'] ?? null;
}
protected function buildDockerComposeCommand(string $command, bool $daemon = true): string
{
return sprintf(
'cd %s && docker compose %s --env-file=%s%s',
$this->filePaths->get('config')->getAbsolutePath(),
$command,
$this->filePaths->get('projectEnv')->getAbsolutePath()
?? $this->filePaths->get('projectEnv')->getProvidedPath(),
$daemon ? ' -d' : ''
);
}
/**
* @throws \Exception
*/
protected function getDefaultPhpVersion(): ?float
{
if (!$this->filePaths->get('defaultSpinnerConfig')?->exists()) {
throw new \Exception('Default spinner configuration file not found.');
}
$config = $this->getDefaultConfig();
return $config['environment']['php']['version'] ?? null;
}
private function setStyle(InputInterface $input, OutputInterface $output): void
{
$this->style = new SymfonyStyle($input, $output);
@ -64,6 +115,9 @@ class AbstractSpinnerCommand extends Command implements ConsoleCommandInterface
{
$this->filePaths = new FilePathCollection([
'config' => new SpinnerFilePath('config'),
'defaultSpinnerConfig' => new SpinnerFilePath('config/spinner.yaml'),
'envTemplate' => new SpinnerFilePath('config/.template.env'),
'data' => new SpinnerFilePath('data'),
]);
}
}

View file

@ -8,24 +8,49 @@ 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', description: 'Spin up a new development environment')]
#[AsCommand(name: 'spin:up', description: 'Spin up a new development environment')]
class SpinCommand extends AbstractSpinnerCommand
{
/**
* @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.');
->addArgument('path', InputArgument::REQUIRED, 'The absolute path to your projects root directory.')
->addOption('php', null, InputOption::VALUE_REQUIRED, 'The PHP version to use (e.g., 8.0).', $this->getDefaultPhpVersion());
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!parent::execute($input, $output)) {
if (parent::execute($input, $output)) {
return Command::FAILURE;
}
if ($this->filePaths->get('projectData')->exists()) {
$this->style->warning('Project data already exists. Skipping new build.');
return Command::SUCCESS;
}
$this->style->success("Spinning up a new development environment...");
mkdir($this->filePaths->get('projectData')->getProvidedPath(), 0777, true);
file_put_contents(
$this->filePaths->get('projectEnv')->getProvidedPath(),
sprintf(
file_get_contents($this->filePaths->get('envTemplate')->getAbsolutePath()),
$this->filePaths->get('project')->getAbsolutePath(),
$input->getArgument('name'),
$input->getOption('php')
)
);
return Command::SUCCESS;
}
}