Compare commits
17 commits
Author | SHA1 | Date | |
---|---|---|---|
3ed4225142 | |||
b235a1c95a | |||
ab18af5b5b | |||
502670c600 | |||
bed48bf026 | |||
70d91a346b | |||
4115526ce8 | |||
5090937085 | |||
b21d0dbfc6 | |||
62e0320413 | |||
be5d6a9e4c | |||
bec504d21c | |||
5d1525234e | |||
c1247f435c | |||
33a5293e17 | |||
ba7af0a81d | |||
42de6d0994 |
13 changed files with 119 additions and 35 deletions
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -2,12 +2,43 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [1.1.2] - 2025-04-26
|
||||||
|
### Fixed
|
||||||
|
- Fixed a critical bug where environments would not build correctly if using a SQLite database.
|
||||||
|
|
||||||
|
## [1.1.1] - 2025-04-26
|
||||||
|
### Fixed
|
||||||
|
- Fixed a critical bug where environments could not be destroyed if using a MySQL database.
|
||||||
|
- Fixed a critical bug where PDO extensions were not installed in the container, causing MySQL driver errors.
|
||||||
|
|
||||||
|
## [1.1.0] - 2025-04-24
|
||||||
|
### Added
|
||||||
|
- New database option: MySQL.
|
||||||
|
- New configuration option: `options.environment.database.rootPassword`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changed the default database for new environments to MySQL (from SQLite).
|
||||||
|
|
||||||
|
## [1.0.4] - 2025-04-24
|
||||||
|
### Fixed
|
||||||
|
- Fixed a critical autoloading issue after the package is globally installed.
|
||||||
|
|
||||||
|
## [1.0.3] - 2025-04-24
|
||||||
|
### Fixed
|
||||||
|
- Fixed typo in `composer.json` description field.
|
||||||
|
|
||||||
|
## [1.0.2] - 2025-04-24
|
||||||
|
### Added
|
||||||
|
- Added information on `spin:down` command to README.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Minor documentation tweak to include a link to the Wiki on Packagist.
|
||||||
|
|
||||||
## [1.0.1] - 2025-04-24
|
## [1.0.1] - 2025-04-24
|
||||||
### Changed
|
### Changed
|
||||||
- Minor documentation tweak to make the header image display on Packagist.
|
- Minor documentation tweak to make the header image display on Packagist.
|
||||||
|
|
||||||
## [1.0.0] – 2025-04-24
|
## [1.0.0] – 2025-04-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Initial public release of **Loom Spinner CLI**.
|
- Initial public release of **Loom Spinner CLI**.
|
||||||
- Command to spin up a new PHP development environment with Docker (`spin:up`).
|
- Command to spin up a new PHP development environment with Docker (`spin:up`).
|
||||||
|
|
15
README.md
15
README.md
|
@ -5,7 +5,7 @@
|
||||||
# Loom Spinner CLI
|
# Loom Spinner CLI
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<img src="https://img.shields.io/badge/Version-1.0.0-blue" alt="Version 1.0.0">
|
<img src="https://img.shields.io/badge/Version-1.1.2-blue" alt="Version 1.1.2">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
A streamlined environment management tool for PHP developers.
|
A streamlined environment management tool for PHP developers.
|
||||||
|
@ -21,16 +21,13 @@ Effortlessly create custom Docker environments for each of your PHP projects. Ou
|
||||||
|
|
||||||
- **PHP 8.4** (includes XDebug & OpCache)
|
- **PHP 8.4** (includes XDebug & OpCache)
|
||||||
- **Nginx**
|
- **Nginx**
|
||||||
- **SQLite3**
|
- **MySQL 9.3**
|
||||||
- **NodeJS 23** (Node, NPM, & NPX)
|
- **NodeJS 23** (Node, NPM, & NPX)
|
||||||
|
|
||||||
Your project directory is automatically mounted to the PHP container, and the `public` directory is served via Nginx at
|
Your project directory is automatically mounted to the PHP container, and the `public` directory is served via Nginx at
|
||||||
`http://localhost:<nginx-port>`. Access the container directly from your terminal to execute unit tests or other
|
`http://localhost:<nginx-port>`. Access the container directly from your terminal to execute unit tests or other
|
||||||
commands, all within an isolated environment.
|
commands, all within an isolated environment.
|
||||||
|
|
||||||
> **Note:** Loom Spinner CLI is in early development. For now, only SQLite is supported as the database, but more options
|
|
||||||
> are on the way in future updates.
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
**Requirements:**
|
**Requirements:**
|
||||||
|
@ -70,7 +67,13 @@ To start them again:
|
||||||
loom spin:start my-project
|
loom spin:start my-project
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To remove them completely:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
loom spin:down my-project
|
||||||
|
```
|
||||||
|
|
||||||
Loom Spinner can be further customized with a set of simple configuration options. For a more detailed quick-start guide
|
Loom Spinner can be further customized with a set of simple configuration options. For a more detailed quick-start guide
|
||||||
or advanced configuration tips, check out the project wiki.
|
or advanced configuration tips, check out the [project wiki](https://forge.winningsoftware.co.uk/LoomLabs/loom-spinner-cli/wiki).
|
||||||
|
|
||||||
Happy spinning! 🧵
|
Happy spinning! 🧵
|
9
bin/loom
9
bin/loom
|
@ -7,7 +7,14 @@ use Loom\Spinner\Command\StartCommand;
|
||||||
use Loom\Spinner\Command\StopCommand;
|
use Loom\Spinner\Command\StopCommand;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
|
|
||||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
$localAutoloadPath = sprintf('%s/vendor/autoload.php', dirname(__DIR__));
|
||||||
|
$installedAutoloadPath = sprintf('%s/autoload.php', dirname(__DIR__, 3));
|
||||||
|
|
||||||
|
if (file_exists($localAutoloadPath)) {
|
||||||
|
require $localAutoloadPath;
|
||||||
|
} else {
|
||||||
|
require $installedAutoloadPath;
|
||||||
|
}
|
||||||
|
|
||||||
$application = new Application('Loom Spinner');
|
$application = new Application('Loom Spinner');
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "loomlabs/loom-spinner-cli",
|
"name": "loomlabs/loom-spinner-cli",
|
||||||
"decsription": "A simple command-line Docker environment spinner for PHP",
|
"description": "A simple command-line Docker environment spinner for PHP.",
|
||||||
"version": "1.0.1",
|
"version": "1.1.2",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Loom\\Spinner\\": "src/"
|
"Loom\\Spinner\\": "src/"
|
||||||
|
@ -13,13 +13,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"loomlabs/utility.collection": "^1.1",
|
|
||||||
"symfony/console": "^7.2",
|
"symfony/console": "^7.2",
|
||||||
"symfony/yaml": "^7.2"
|
"symfony/yaml": "^7.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "^12.1"
|
|
||||||
},
|
|
||||||
"bin": [
|
"bin": [
|
||||||
"bin/loom"
|
"bin/loom"
|
||||||
],
|
],
|
||||||
|
|
|
@ -7,4 +7,8 @@ PHP_VERSION=%s
|
||||||
PHP_PORT=%s
|
PHP_PORT=%s
|
||||||
|
|
||||||
# Nginx
|
# Nginx
|
||||||
NGINX_PORT=%s
|
SERVER_PORT=%s
|
||||||
|
|
||||||
|
# Database
|
||||||
|
DATABASE_PORT=%s
|
||||||
|
ROOT_PASSWORD=%s
|
16
config/mysql.yaml
Normal file
16
config/mysql.yaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:9.3.0
|
||||||
|
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
|
||||||
|
ports:
|
||||||
|
- ${DATABASE_PORT}:3306
|
||||||
|
expose:
|
||||||
|
- "3306"
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: ${ROOT_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql:cached
|
||||||
|
container_name: ${PROJECT_NAME}-mysql
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
|
@ -2,9 +2,9 @@ services:
|
||||||
nginx:
|
nginx:
|
||||||
build:
|
build:
|
||||||
context: ./nginx
|
context: ./nginx
|
||||||
|
container_name: ${PROJECT_NAME}-nginx
|
||||||
ports:
|
ports:
|
||||||
- ${NGINX_PORT}:80
|
- ${SERVER_PORT}:80
|
||||||
volumes:
|
volumes:
|
||||||
- ${PROJECT_DIRECTORY}:/var/www/html:cached
|
- ${PROJECT_DIRECTORY}:/var/www/html:cached
|
||||||
- ./nginx/conf.d:/etc/nginx/conf.d
|
- ./nginx/conf.d:/etc/nginx/conf.d
|
||||||
container_name: ${PROJECT_NAME}-nginx
|
|
1
config/php-fpm/MySQL.Dockerfile
Normal file
1
config/php-fpm/MySQL.Dockerfile
Normal file
|
@ -0,0 +1 @@
|
||||||
|
RUN docker-php-ext-install mysqli pdo pdo_mysql > /dev/null 2>&1
|
|
@ -9,4 +9,5 @@ options:
|
||||||
enabled: true
|
enabled: true
|
||||||
database:
|
database:
|
||||||
enabled: true
|
enabled: true
|
||||||
driver: sqlite3
|
driver: mysql
|
||||||
|
rootPassword: docker
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||||
namespace Loom\Spinner\Classes\File;
|
namespace Loom\Spinner\Classes\File;
|
||||||
|
|
||||||
use Loom\Spinner\Classes\Config\Config;
|
use Loom\Spinner\Classes\Config\Config;
|
||||||
use Loom\Utility\FilePath\FilePath;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
|
||||||
class DockerComposeFileBuilder extends AbstractFileBuilder
|
class DockerComposeFileBuilder extends AbstractFileBuilder
|
||||||
|
@ -13,7 +12,7 @@ class DockerComposeFileBuilder extends AbstractFileBuilder
|
||||||
/**
|
/**
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function __construct(Config $config)
|
public function __construct(Config $config, private array $ports)
|
||||||
{
|
{
|
||||||
return parent::__construct($config->getDataDirectory() . '/docker-compose.yaml', $config);
|
return parent::__construct($config->getDataDirectory() . '/docker-compose.yaml', $config);
|
||||||
}
|
}
|
||||||
|
@ -25,13 +24,20 @@ class DockerComposeFileBuilder extends AbstractFileBuilder
|
||||||
{
|
{
|
||||||
$this->content = $this->config->getConfigFileContents('php.yaml');
|
$this->content = $this->config->getConfigFileContents('php.yaml');
|
||||||
|
|
||||||
if ($this->config->isDatabaseEnabled($input) && in_array($this->config->getDatabaseDriver($input), ['sqlite3', 'sqlite'])) {
|
|
||||||
$this->addSqliteDatabaseConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->config->isServerEnabled($input)) {
|
if ($this->config->isServerEnabled($input)) {
|
||||||
$this->addNginxConfig();
|
$this->addNginxConfig();
|
||||||
}
|
}
|
||||||
|
if ($this->config->isDatabaseEnabled($input)) {
|
||||||
|
$databaseDriver = strtolower($this->config->getDatabaseDriver($input));
|
||||||
|
|
||||||
|
if (in_array($databaseDriver, ['sqlite3', 'sqlite'])) {
|
||||||
|
$this->addSqliteDatabaseConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($databaseDriver === 'mysql') {
|
||||||
|
$this->addMysqlDatabaseConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -56,4 +62,12 @@ class DockerComposeFileBuilder extends AbstractFileBuilder
|
||||||
$sqlLiteConfig = str_replace('volumes:', '', $sqlLiteConfig);
|
$sqlLiteConfig = str_replace('volumes:', '', $sqlLiteConfig);
|
||||||
$this->content .= $sqlLiteConfig;
|
$this->content .= $sqlLiteConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function addMysqlDatabaseConfig(): void
|
||||||
|
{
|
||||||
|
$mysqlConfig = str_replace('services:', '', $this->config->getConfigFileContents('mysql.yaml'));
|
||||||
|
$mysqlConfig = str_replace('${ROOT_PASSWORD}', $this->config->getEnvironmentOption('database', 'rootPassword'), $mysqlConfig);
|
||||||
|
$mysqlConfig = str_replace('${DATABASE_PORT}', (string) $this->ports['database'], $mysqlConfig);
|
||||||
|
$this->content.= $mysqlConfig;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -31,16 +31,23 @@ class PHPDockerFileBuilder extends AbstractFileBuilder
|
||||||
$this->config->getConfigFileContents('php-fpm/opcache.ini')
|
$this->config->getConfigFileContents('php-fpm/opcache.ini')
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($this->config->isDatabaseEnabled($input) && in_array($this->config->getDatabaseDriver($input), ['sqlite3', 'sqlite'])) {
|
if ($this->config->isDatabaseEnabled($input)) {
|
||||||
$this->addNewLine();
|
if (in_array($this->config->getDatabaseDriver($input), ['sqlite3', 'sqlite'])) {
|
||||||
$this->content .= $this->config->getConfigFileContents('php-fpm/Sqlite.Dockerfile');
|
$this->addNewLine();
|
||||||
|
$this->content .= $this->config->getConfigFileContents('php-fpm/Sqlite.Dockerfile');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->config->getDatabaseDriver($input) ==='mysql') {
|
||||||
|
$this->addNewLine();
|
||||||
|
$this->content .= $this->config->getConfigFileContents('php-fpm/MySQL.Dockerfile');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->content = str_replace('${NODE_VERSION}', (string) $this->config->getNodeVersion($input), $this->content);
|
$this->content = str_replace('${NODE_VERSION}', (string) $this->config->getNodeVersion($input), $this->content);
|
||||||
|
|
||||||
if ($this->config->isXdebugEnabled($input)) {
|
if ($this->config->isXdebugEnabled($input)) {
|
||||||
$this->addNewLine();
|
$this->addNewLine();
|
||||||
$this->content .= $this->config->getConfigFileContents('php-fpm/Xdebug.Dockerfile');
|
$this->content .= $this->config->getConfigFileContents('php-fpm/XDebug.Dockerfile');
|
||||||
file_put_contents(
|
file_put_contents(
|
||||||
$this->config->getDataDirectory() . '/php-fpm/xdebug.ini',
|
$this->config->getDataDirectory() . '/php-fpm/xdebug.ini',
|
||||||
$this->config->getConfigFileContents('php-fpm/xdebug.ini')
|
$this->config->getConfigFileContents('php-fpm/xdebug.ini')
|
||||||
|
|
|
@ -29,7 +29,8 @@ class DestroyCommand extends AbstractSpinnerCommand
|
||||||
return Command::FAILURE;
|
return Command::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->config = new Config($input->getArgument('name'));
|
$projectName = $input->getArgument('name');
|
||||||
|
$this->config = new Config($projectName);
|
||||||
|
|
||||||
if (!file_exists($this->config->getDataDirectory())) {
|
if (!file_exists($this->config->getDataDirectory())) {
|
||||||
$this->style->error('No project found with the provided name.');
|
$this->style->error('No project found with the provided name.');
|
||||||
|
@ -38,7 +39,7 @@ class DestroyCommand extends AbstractSpinnerCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
passthru($this->buildDockerComposeCommand('down', false, false));
|
passthru($this->buildDockerComposeCommand('down -v', false, false));
|
||||||
recursive_rmdir($this->config->getDataDirectory());
|
recursive_rmdir($this->config->getDataDirectory());
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
$this->style->error('An error occurred while destroying the project: ' . $exception->getMessage());
|
$this->style->error('An error occurred while destroying the project: ' . $exception->getMessage());
|
||||||
|
|
|
@ -28,7 +28,8 @@ class SpinCommand extends AbstractSpinnerCommand
|
||||||
$this->portGenerator = new PortGenerator();
|
$this->portGenerator = new PortGenerator();
|
||||||
$this->ports = [
|
$this->ports = [
|
||||||
'php' => $this->portGenerator->generateRandomPort(),
|
'php' => $this->portGenerator->generateRandomPort(),
|
||||||
'nginx' => $this->portGenerator->generateRandomPort(),
|
'server' => $this->portGenerator->generateRandomPort(),
|
||||||
|
'database' => $this->portGenerator->generateRandomPort(),
|
||||||
];
|
];
|
||||||
|
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
@ -66,7 +67,7 @@ class SpinCommand extends AbstractSpinnerCommand
|
||||||
InputOption::VALUE_NONE,
|
InputOption::VALUE_NONE,
|
||||||
'Set this flag to not include a database for your environment.'
|
'Set this flag to not include a database for your environment.'
|
||||||
)
|
)
|
||||||
->addOption('database', null, InputOption::VALUE_REQUIRED, 'The type of database to use (e.g., mysql, postgresql, sqlite).', null, ['sqlite'])
|
->addOption('database', null, InputOption::VALUE_REQUIRED, 'The type of database to use (e.g., mysql, sqlite).', null, ['mysql', 'sqlite'])
|
||||||
->addOption('node', null, InputOption::VALUE_OPTIONAL, 'The Node.js version to use (e.g. 20).');
|
->addOption('node', null, InputOption::VALUE_OPTIONAL, 'The Node.js version to use (e.g. 20).');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +155,9 @@ class SpinCommand extends AbstractSpinnerCommand
|
||||||
$input->getArgument('name'),
|
$input->getArgument('name'),
|
||||||
$this->config->getPhpVersion($input),
|
$this->config->getPhpVersion($input),
|
||||||
$this->ports['php'],
|
$this->ports['php'],
|
||||||
$this->ports['nginx'],
|
$this->ports['server'],
|
||||||
|
$this->ports['database'],
|
||||||
|
$this->config->getEnvironmentOption('database', 'rootPassword')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -166,7 +169,7 @@ class SpinCommand extends AbstractSpinnerCommand
|
||||||
{
|
{
|
||||||
$this->createProjectDataSubDirectory('php-fpm');
|
$this->createProjectDataSubDirectory('php-fpm');
|
||||||
|
|
||||||
(new DockerComposeFileBuilder($this->config))->build($input)->save();
|
(new DockerComposeFileBuilder($this->config, $this->ports))->build($input)->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Reference in a new issue