mirror of
https://github.com/opdavies/build-configs.git
synced 2025-01-22 18:27:31 +00:00
refactor(*): change to a Symfony Console app
This commit is contained in:
parent
4af661bad4
commit
8db64458b1
20
.env
Normal file
20
.env
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# In all environments, the following files are loaded if they exist,
|
||||||
|
# the latter taking precedence over the former:
|
||||||
|
#
|
||||||
|
# * .env contains default values for the environment variables needed by the app
|
||||||
|
# * .env.local uncommitted file with local overrides
|
||||||
|
# * .env.$APP_ENV committed environment-specific defaults
|
||||||
|
# * .env.$APP_ENV.local uncommitted environment-specific overrides
|
||||||
|
#
|
||||||
|
# Real environment variables win over .env files.
|
||||||
|
#
|
||||||
|
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
|
||||||
|
#
|
||||||
|
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
||||||
|
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
APP_ENV=prod
|
||||||
|
APP_DEBUG=0
|
||||||
|
APP_SECRET=91b4670ae7b240ca6b97ed20f81c81f0
|
||||||
|
###< symfony/framework-bundle ###
|
28
.github/workflows/ci.yml
vendored
28
.github/workflows/ci.yml
vendored
|
@ -1,28 +0,0 @@
|
||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
main:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- uses: extractions/setup-just@95b912dc5d3ed106a72907f2f9b91e76d60bdb76 # 1.5.0
|
|
||||||
|
|
||||||
- name: Build the Docker image
|
|
||||||
run: |
|
|
||||||
docker image build . \
|
|
||||||
--tag build-configs
|
|
||||||
|
|
||||||
- name: Run PHPStan
|
|
||||||
run: |
|
|
||||||
docker run \
|
|
||||||
--rm \
|
|
||||||
--interactive \
|
|
||||||
--entrypoint phpstan \
|
|
||||||
build-configs \
|
|
||||||
--no-progress
|
|
13
.gitignore
vendored
13
.gitignore
vendored
|
@ -1,3 +1,12 @@
|
||||||
/.phpunit.result.cache
|
/build-configs
|
||||||
/build/
|
/**/vendor/
|
||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
/.env.local
|
||||||
|
/.env.local.php
|
||||||
|
/.env.*.local
|
||||||
|
/config/secrets/prod/prod.decrypt.private.php
|
||||||
|
/public/bundles/
|
||||||
|
/var/
|
||||||
/vendor/
|
/vendor/
|
||||||
|
###< symfony/framework-bundle ###
|
||||||
|
|
36
autoload_runtime.template
Normal file
36
autoload_runtime.template
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_runtime.php @generated by Symfony Runtime
|
||||||
|
|
||||||
|
if (true === (require_once __DIR__.'/autoload.php') || empty($_SERVER['SCRIPT_FILENAME'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pharPath = Phar::running();
|
||||||
|
|
||||||
|
if (strlen($pharPath) == 0) {
|
||||||
|
$scriptFileName = $_SERVER['SCRIPT_FILENAME'];
|
||||||
|
} else {
|
||||||
|
$scriptFileName = $_SERVER['APP_SCRIPT_FILENAME'] ?? $_SERVER['SCRIPT_FILENAME'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$app = require $scriptFileName;
|
||||||
|
|
||||||
|
if (!is_object($app)) {
|
||||||
|
throw new TypeError(sprintf('Invalid return value: callable object expected, "%s" returned from "%s".', get_debug_type($app), $_SERVER['SCRIPT_FILENAME']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? %runtime_class%;
|
||||||
|
$runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + %runtime_options%);
|
||||||
|
|
||||||
|
[$app, $args] = $runtime
|
||||||
|
->getResolver($app)
|
||||||
|
->resolve();
|
||||||
|
|
||||||
|
$app = $app(...$args);
|
||||||
|
|
||||||
|
exit(
|
||||||
|
$runtime
|
||||||
|
->getRunner($app)
|
||||||
|
->run()
|
||||||
|
);
|
|
@ -1,266 +1,19 @@
|
||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
use App\Kernel;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||||
|
|
||||||
require __DIR__.'/../vendor/autoload.php';
|
$_SERVER['APP_SCRIPT_FILENAME'] = __FILE__;
|
||||||
|
|
||||||
use Illuminate\Support\{Arr, Collection};
|
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
|
||||||
use OliverDaviesLtd\BuildConfigs\ConfigurationData;
|
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
||||||
use OliverDaviesLtd\BuildConfigs\DataTransferObject\TemplateFile;
|
|
||||||
use OliverDaviesLtd\BuildConfigs\Enum\{Language, WebServer};
|
|
||||||
use Silly\Application;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
|
||||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
|
||||||
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
|
||||||
use Symfony\Component\Serializer\Serializer;
|
|
||||||
use Symfony\Component\Validator\{ConstraintViolationInterface, Validation};
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
use Twig\Environment;
|
|
||||||
use Twig\Loader\FilesystemLoader;
|
|
||||||
|
|
||||||
$app = new Application();
|
|
||||||
|
|
||||||
$app->command(
|
|
||||||
'init [-p|--project-name=] [-l|--language=] [-t|--type=]',
|
|
||||||
function(
|
|
||||||
string $projectName,
|
|
||||||
string $language,
|
|
||||||
string $type,
|
|
||||||
): void {
|
|
||||||
$projectName = str_replace('.', '-', $projectName);
|
|
||||||
|
|
||||||
// TODO: validate the project type.
|
|
||||||
$output = <<<EOF
|
|
||||||
name: $projectName
|
|
||||||
language: $language
|
|
||||||
type: $type
|
|
||||||
EOF;
|
|
||||||
|
|
||||||
file_put_contents('build.yaml', $output);
|
|
||||||
}
|
|
||||||
)->descriptions('Initialise a new build.yaml file.', [
|
|
||||||
'--project-name' => 'The name of the project.',
|
|
||||||
'--language' => 'The language used in the project.',
|
|
||||||
'--type' => 'The project type.',
|
|
||||||
]);;
|
|
||||||
|
|
||||||
$app->command(
|
|
||||||
'generate [-c|--config-file=] [-o|--output-dir=]',
|
|
||||||
function (
|
|
||||||
SymfonyStyle $io,
|
|
||||||
string $configFile = 'build.yaml',
|
|
||||||
string $outputDir = '.',
|
|
||||||
): void {
|
|
||||||
$configurationData = array_merge(
|
|
||||||
Yaml::parseFile(__DIR__ . '/../resources/build.defaults.yaml'),
|
|
||||||
Yaml::parseFile($configFile),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Convert the input to a configuration data object.
|
|
||||||
$normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter());
|
|
||||||
$serializer = new Serializer([$normalizer], [new JsonEncoder()]);
|
|
||||||
$configurationDataObject = $serializer->deserialize(json_encode($configurationData), ConfigurationData::class, 'json');
|
|
||||||
|
|
||||||
$validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();
|
|
||||||
$violations = $validator->validate($configurationDataObject);
|
|
||||||
|
|
||||||
if (0 < $violations->count()) {
|
|
||||||
$io->error('Configuration is invalid.');
|
|
||||||
|
|
||||||
$io->listing(
|
|
||||||
collect($violations)
|
|
||||||
->map(fn (ConstraintViolationInterface $v) => "{$v->getPropertyPath()} - {$v->getMessage()}")
|
|
||||||
->toArray()
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($configurationData['docker-compose'])) {
|
|
||||||
$configurationData['dockerCompose'] = $configurationData['docker-compose'];
|
|
||||||
$configurationData['docker-compose'] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$configurationData['managedText'] = 'Do not edit this file. It is automatically generated by https://www.oliverdavies.uk/build-configs.';
|
|
||||||
|
|
||||||
$filesToGenerate = getFiles(configurationData: $configurationData);
|
|
||||||
|
|
||||||
$io->info("Building configuration for {$configurationData['name']}.");
|
|
||||||
|
|
||||||
$io->write('Generated files:');
|
|
||||||
$io->listing(getListOfFiles(filesToGenerate: $filesToGenerate)->toArray());
|
|
||||||
|
|
||||||
generateFiles(
|
|
||||||
configurationData: $configurationData,
|
|
||||||
filesToGenerate: $filesToGenerate,
|
|
||||||
outputDir: $outputDir,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)->descriptions('Generate project-specific configuration files.', [
|
|
||||||
'--config-file' => 'The path to the project\'s build.yaml file',
|
|
||||||
'--output-dir' => 'The directory to create files in',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$app->run();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, string> $configurationData
|
|
||||||
* @param Collection<int, TemplateFile> $filesToGenerate
|
|
||||||
*/
|
|
||||||
function generateFiles(
|
|
||||||
Collection $filesToGenerate,
|
|
||||||
string $outputDir,
|
|
||||||
array $configurationData,
|
|
||||||
): void
|
|
||||||
{
|
|
||||||
$filesystem = new Filesystem();
|
|
||||||
$twig = new Environment(new FilesystemLoader([__DIR__ . '/../templates']));
|
|
||||||
|
|
||||||
$filesToGenerate->each(function(TemplateFile $templateFile) use ($configurationData, $filesystem, $outputDir, $twig): void {
|
|
||||||
if ($templateFile->path !== null) {
|
|
||||||
if (!$filesystem->exists($templateFile->path)) {
|
|
||||||
$filesystem->mkdir("{$outputDir}/{$templateFile->path}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$sourceFile = "{$templateFile->data}.twig";
|
|
||||||
$outputFile = collect([
|
|
||||||
$outputDir,
|
|
||||||
$templateFile->path,
|
|
||||||
$templateFile->name,
|
|
||||||
])->filter()->implode('/');
|
|
||||||
|
|
||||||
$filesystem->dumpFile($outputFile, $twig->render($sourceFile, $configurationData));
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the Docker entrypoint file is generated, ensure it is executable.
|
|
||||||
if ($filesystem->exists("{$outputDir}/tools/docker/images/php/root/usr/local/bin/docker-entrypoint-php")) {
|
|
||||||
$filesystem->chmod("{$outputDir}/tools/docker/images/php/root/usr/local/bin/docker-entrypoint-php", 0755);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFiles(array $configurationData): Collection
|
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||||
{
|
|
||||||
/** @var Collection<int, TemplateFile> */
|
|
||||||
$filesToGenerate = collect([
|
|
||||||
new TemplateFile(data: 'common/.dockerignore', name: '.dockerignore'),
|
|
||||||
new TemplateFile(data: 'common/.hadolint.yaml', name: '.hadolint.yaml'),
|
|
||||||
new TemplateFile(data: 'env.example', name: '.env.example'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$extraDatabases = Arr::get($configurationData, 'database.extra_databases', []);
|
return static function (array $context) {
|
||||||
if (count($extraDatabases) > 0) {
|
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||||
$filesToGenerate[] = new TemplateFile(
|
|
||||||
data: 'extra-databases.sql',
|
|
||||||
name: 'extra-databases.sql',
|
|
||||||
path: 'tools/docker/images/database/root/docker-entrypoint-initdb.d',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false !== Arr::get($configurationData, "justfile", true)) {
|
return new Application($kernel);
|
||||||
$filesToGenerate[] = new TemplateFile(data: 'justfile', name: 'justfile');
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($configurationData['dockerCompose']) && $configurationData['dockerCompose'] !== null) {
|
|
||||||
$filesToGenerate[] = new TemplateFile(data: 'docker-compose.yaml', name: 'docker-compose.yaml');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPhp(Arr::get($configurationData, 'language'))) {
|
|
||||||
$filesToGenerate[] = new TemplateFile(data: 'php/Dockerfile', name: 'Dockerfile');
|
|
||||||
$filesToGenerate[] = new TemplateFile(data: 'php/phpcs.xml', name: 'phpcs.xml.dist');
|
|
||||||
$filesToGenerate[] = new TemplateFile(data: 'php/phpunit.xml', name: 'phpunit.xml.dist');
|
|
||||||
$filesToGenerate[] = new TemplateFile(
|
|
||||||
data: 'php/docker-entrypoint-php',
|
|
||||||
name: 'docker-entrypoint-php',
|
|
||||||
path: 'tools/docker/images/php/root/usr/local/bin',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Arr::has(array: $configurationData, keys: 'php.phpstan')) {
|
|
||||||
$filesToGenerate[] = new TemplateFile(data: 'php/phpstan.neon', name: 'phpstan.neon.dist');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNode(Arr::get($configurationData, 'language'))) {
|
|
||||||
$filesToGenerate[] = new TemplateFile(data: 'node/.yarnrc', name: '.yarnrc');
|
|
||||||
$filesToGenerate[] = new TemplateFile(data: 'node/Dockerfile', name: 'Dockerfile');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCaddy(Arr::get($configurationData, 'web.type'))) {
|
|
||||||
$filesToGenerate[] = new TemplateFile(
|
|
||||||
data: 'web/caddy/Caddyfile',
|
|
||||||
name: 'Caddyfile',
|
|
||||||
path: 'tools/docker/images/web/root/etc/caddy',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNginx(Arr::get($configurationData, 'web.type'))) {
|
|
||||||
$filesToGenerate[] = new TemplateFile(
|
|
||||||
data: 'web/nginx/default.conf',
|
|
||||||
name: 'default.conf',
|
|
||||||
path: 'tools/docker/images/web/root/etc/nginx/conf.d',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('drupal-project' === Arr::get($configurationData, 'type')) {
|
|
||||||
// Add a Drupal version of phpunit.xml.dist.
|
|
||||||
$filesToGenerate[] = new TemplateFile(data: 'drupal-project/phpunit.xml.dist', name: 'phpunit.xml.dist');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Arr::get($configurationData, 'experimental.createGitHubActionsConfiguration', false) === true) {
|
|
||||||
$filesToGenerate[] = new TemplateFile(
|
|
||||||
data: 'ci/github-actions/ci.yml',
|
|
||||||
name: 'ci.yml',
|
|
||||||
path: '.github/workflows',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $filesToGenerate;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getListOfFiles(Collection $filesToGenerate): Collection
|
|
||||||
{
|
|
||||||
return $filesToGenerate
|
|
||||||
->map(fn (TemplateFile $templateFile): string =>
|
|
||||||
collect([$templateFile->path, $templateFile->name])->filter()->implode('/'))
|
|
||||||
->unique()
|
|
||||||
->sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isCaddy(?string $webServer): bool
|
|
||||||
{
|
|
||||||
if (is_null($webServer)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return strtoupper($webServer) === WebServer::CADDY->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isNginx(?string $webServer): bool
|
|
||||||
{
|
|
||||||
if (is_null($webServer)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return strtoupper($webServer) === WebServer::NGINX->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isNode(?string $language): bool
|
|
||||||
{
|
|
||||||
if (is_null($language)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return strtoupper($language) === Language::NODE->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPhp(?string $language): bool
|
|
||||||
{
|
|
||||||
if (is_null($language)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return strtoupper($language) === Language::PHP->name;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
{
|
{
|
||||||
"compression": "GZ",
|
"output": "build-configs",
|
||||||
"directories": [
|
"files-bin": [
|
||||||
"src",
|
".env.local.php",
|
||||||
"templates",
|
"autoload_runtime.template",
|
||||||
"vendor"
|
"src/Controller/.gitignore"
|
||||||
],
|
],
|
||||||
"main": "bin/build-configs",
|
"directories": [
|
||||||
"output": "build/build-configs"
|
"config",
|
||||||
|
"public",
|
||||||
|
"resources",
|
||||||
|
"var"
|
||||||
|
],
|
||||||
|
"force-autodiscovery": true,
|
||||||
|
"check-requirements": false,
|
||||||
|
"exclude-composer-files": false,
|
||||||
|
"compression": "GZ"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,71 @@
|
||||||
{
|
{
|
||||||
|
"type": "project",
|
||||||
|
"license": "proprietary",
|
||||||
"require": {
|
"require": {
|
||||||
"illuminate/support": "^9.50",
|
"php": "^8.1",
|
||||||
"mnapoli/silly": "^1.8",
|
"ext-ctype": "*",
|
||||||
"symfony/console": "^6.2",
|
"ext-iconv": "*",
|
||||||
"symfony/filesystem": "^6.2",
|
"doctrine/annotations": "^2.0",
|
||||||
"symfony/property-access": "^6.2",
|
"illuminate/collections": "*",
|
||||||
"symfony/serializer": "^6.2",
|
"illuminate/support": "^10.8",
|
||||||
"symfony/validator": "^6.2",
|
"phpdocumentor/reflection-docblock": "^5.3",
|
||||||
"symfony/yaml": "^6.2",
|
"phpstan/phpdoc-parser": "^1.20",
|
||||||
"twig/twig": "^3.5"
|
"phpstan/phpstan": "^1.10",
|
||||||
},
|
"symfony/console": "6.2.*",
|
||||||
"require-dev": {
|
"symfony/dotenv": "6.2.*",
|
||||||
"humbug/box": "^4.2",
|
"symfony/flex": "^2.0",
|
||||||
"pestphp/pest": "^1.22",
|
"symfony/framework-bundle": "6.2.*",
|
||||||
"phpstan/phpstan": "^1.9",
|
"symfony/property-access": "6.2.*",
|
||||||
"symfony/var-dumper": "^6.2"
|
"symfony/property-info": "6.2.*",
|
||||||
},
|
"symfony/runtime": "6.2.*",
|
||||||
"autoload": {
|
"symfony/serializer": "6.2.*",
|
||||||
"psr-4": {
|
"symfony/twig-bundle": "6.2.*",
|
||||||
"OliverDaviesLtd\\BuildConfigs\\": "src/"
|
"symfony/validator": "6.2.*",
|
||||||
}
|
"symfony/yaml": "6.2.*",
|
||||||
|
"twig/extra-bundle": "^2.12|^3.0",
|
||||||
|
"twig/twig": "^2.12|^3.0"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
|
"platform": {
|
||||||
|
"php": "8.2"
|
||||||
|
},
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"pestphp/pest-plugin": true
|
"symfony/flex": true,
|
||||||
|
"symfony/runtime": true,
|
||||||
|
"bamarni/composer-bin-plugin": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"bin": ["bin/build-configs"],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"replace": {
|
||||||
|
"symfony/polyfill-ctype": "*",
|
||||||
|
"symfony/polyfill-iconv": "*",
|
||||||
|
"symfony/polyfill-php72": "*",
|
||||||
|
"symfony/polyfill-php73": "*",
|
||||||
|
"symfony/polyfill-php74": "*",
|
||||||
|
"symfony/polyfill-php80": "*",
|
||||||
|
"symfony/polyfill-php81": "*"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/symfony": "*"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"bin-dir": "new-bin",
|
||||||
|
"symfony": {
|
||||||
|
"allow-contrib": false,
|
||||||
|
"require": "6.2.*"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"autoload_template": "autoload_runtime.template"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"bamarni/composer-bin-plugin": "^1.8",
|
||||||
|
"symfony/maker-bundle": "^1.48"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
5748
composer.lock
generated
5748
composer.lock
generated
File diff suppressed because it is too large
Load diff
8
config/bundles.php
Normal file
8
config/bundles.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||||
|
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||||
|
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||||
|
];
|
19
config/packages/cache.yaml
Normal file
19
config/packages/cache.yaml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
framework:
|
||||||
|
cache:
|
||||||
|
# Unique name of your app: used to compute stable namespaces for cache keys.
|
||||||
|
#prefix_seed: your_vendor_name/app_name
|
||||||
|
|
||||||
|
# The "app" cache stores to the filesystem by default.
|
||||||
|
# The data in this cache should persist between deploys.
|
||||||
|
# Other options include:
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
#app: cache.adapter.redis
|
||||||
|
#default_redis_provider: redis://localhost
|
||||||
|
|
||||||
|
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
|
||||||
|
#app: cache.adapter.apcu
|
||||||
|
|
||||||
|
# Namespaced pools use the above "app" backend by default
|
||||||
|
#pools:
|
||||||
|
#my.dedicated.cache: null
|
3
config/packages/dev/routing.yaml
Normal file
3
config/packages/dev/routing.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
framework:
|
||||||
|
router:
|
||||||
|
strict_requirements: true
|
17
config/packages/framework.yaml
Normal file
17
config/packages/framework.yaml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||||
|
framework:
|
||||||
|
secret: '%env(APP_SECRET)%'
|
||||||
|
#csrf_protection: true
|
||||||
|
#http_method_override: true
|
||||||
|
|
||||||
|
# Enables session support. Note that the session will ONLY be started if you read or write from it.
|
||||||
|
# Remove or comment this section to explicitly disable session support.
|
||||||
|
session:
|
||||||
|
handler_id: null
|
||||||
|
cookie_secure: auto
|
||||||
|
cookie_samesite: lax
|
||||||
|
|
||||||
|
#esi: true
|
||||||
|
#fragments: true
|
||||||
|
php_errors:
|
||||||
|
log: true
|
3
config/packages/prod/routing.yaml
Normal file
3
config/packages/prod/routing.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
framework:
|
||||||
|
router:
|
||||||
|
strict_requirements: null
|
7
config/packages/routing.yaml
Normal file
7
config/packages/routing.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
framework:
|
||||||
|
router:
|
||||||
|
utf8: true
|
||||||
|
|
||||||
|
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||||
|
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||||
|
#default_uri: http://localhost
|
4
config/packages/test/framework.yaml
Normal file
4
config/packages/test/framework.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
framework:
|
||||||
|
test: true
|
||||||
|
session:
|
||||||
|
storage_id: session.storage.mock_file
|
3
config/packages/test/routing.yaml
Normal file
3
config/packages/test/routing.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
framework:
|
||||||
|
router:
|
||||||
|
strict_requirements: true
|
13
config/packages/translation.yaml
Normal file
13
config/packages/translation.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
framework:
|
||||||
|
default_locale: en
|
||||||
|
translator:
|
||||||
|
default_path: '%kernel.project_dir%/translations'
|
||||||
|
fallbacks:
|
||||||
|
- en
|
||||||
|
# providers:
|
||||||
|
# crowdin:
|
||||||
|
# dsn: '%env(CROWDIN_DSN)%'
|
||||||
|
# loco:
|
||||||
|
# dsn: '%env(LOCO_DSN)%'
|
||||||
|
# lokalise:
|
||||||
|
# dsn: '%env(LOKALISE_DSN)%'
|
6
config/packages/twig.yaml
Normal file
6
config/packages/twig.yaml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
twig:
|
||||||
|
default_path: '%kernel.project_dir%/templates'
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
twig:
|
||||||
|
strict_variables: true
|
13
config/packages/validator.yaml
Normal file
13
config/packages/validator.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
framework:
|
||||||
|
validation:
|
||||||
|
email_validation_mode: html5
|
||||||
|
|
||||||
|
# Enables validator auto-mapping support.
|
||||||
|
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
||||||
|
#auto_mapping:
|
||||||
|
# App\Entity\: []
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
framework:
|
||||||
|
validation:
|
||||||
|
not_compromised_password: false
|
17
config/preload.php
Normal file
17
config/preload.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the box project.
|
||||||
|
*
|
||||||
|
* (c) Kevin Herrera <kevin@herrera.io>
|
||||||
|
* Théo Fidry <theo.fidry@gmail.com>
|
||||||
|
*
|
||||||
|
* This source file is subject to the MIT license that is bundled
|
||||||
|
* with this source code in the file LICENSE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
|
||||||
|
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
|
||||||
|
}
|
3
config/routes.yaml
Normal file
3
config/routes.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#index:
|
||||||
|
# path: /
|
||||||
|
# controller: App\Controller\DefaultController::index
|
3
config/routes/dev/framework.yaml
Normal file
3
config/routes/dev/framework.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
_errors:
|
||||||
|
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||||
|
prefix: /_error
|
31
config/services.yaml
Normal file
31
config/services.yaml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# This file is the entry point to configure your own services.
|
||||||
|
# Files in the packages/ subdirectory configure your dependencies.
|
||||||
|
|
||||||
|
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||||
|
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
|
||||||
|
parameters:
|
||||||
|
|
||||||
|
services:
|
||||||
|
# default configuration for services in *this* file
|
||||||
|
_defaults:
|
||||||
|
autowire: true # Automatically injects dependencies in your services.
|
||||||
|
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||||
|
|
||||||
|
# makes classes in src/ available to be used as services
|
||||||
|
# this creates a service per class whose id is the fully-qualified class name
|
||||||
|
App\:
|
||||||
|
resource: '../src/'
|
||||||
|
exclude:
|
||||||
|
- '../src/DependencyInjection/'
|
||||||
|
- '../src/Entity/'
|
||||||
|
- '../src/Kernel.php'
|
||||||
|
- '../src/Tests/'
|
||||||
|
|
||||||
|
# controllers are imported separately to make sure services can be injected
|
||||||
|
# as action arguments even if you don't extend any base controller class
|
||||||
|
App\Controller\:
|
||||||
|
resource: '../src/Controller/'
|
||||||
|
tags: ['controller.service_arguments']
|
||||||
|
|
||||||
|
# add more service definitions when explicit configuration is needed
|
||||||
|
# please note that last definitions always *replace* previous ones
|
3
justfile
3
justfile
|
@ -1,5 +1,6 @@
|
||||||
_default:
|
_default:
|
||||||
@just --list
|
@just --list
|
||||||
|
|
||||||
build:
|
compile:
|
||||||
|
composer dump-env prod
|
||||||
./vendor/bin/box compile
|
./vendor/bin/box compile
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
parameters:
|
parameters:
|
||||||
level: 8
|
level: 5
|
||||||
paths:
|
paths:
|
||||||
- bin
|
|
||||||
- src
|
- src
|
||||||
|
|
18
phpunit.xml
18
phpunit.xml
|
@ -1,18 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
|
||||||
bootstrap="vendor/autoload.php"
|
|
||||||
colors="true"
|
|
||||||
>
|
|
||||||
<testsuites>
|
|
||||||
<testsuite name="Test Suite">
|
|
||||||
<directory suffix="Test.php">./tests</directory>
|
|
||||||
</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
<coverage processUncoveredFiles="true">
|
|
||||||
<include>
|
|
||||||
<directory suffix=".php">./app</directory>
|
|
||||||
<directory suffix=".php">./src</directory>
|
|
||||||
</include>
|
|
||||||
</coverage>
|
|
||||||
</phpunit>
|
|
34
public/index.php
Normal file
34
public/index.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the box project.
|
||||||
|
*
|
||||||
|
* (c) Kevin Herrera <kevin@herrera.io>
|
||||||
|
* Théo Fidry <theo.fidry@gmail.com>
|
||||||
|
*
|
||||||
|
* This source file is subject to the MIT license that is bundled
|
||||||
|
* with this source code in the file LICENSE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use App\Kernel;
|
||||||
|
use Symfony\Component\Dotenv\Dotenv;
|
||||||
|
use Symfony\Component\ErrorHandler\Debug;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
require dirname(__DIR__).'/vendor/autoload.php';
|
||||||
|
|
||||||
|
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
|
||||||
|
|
||||||
|
if ($_SERVER['APP_DEBUG']) {
|
||||||
|
umask(0);
|
||||||
|
|
||||||
|
Debug::enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
|
||||||
|
$request = Request::createFromGlobals();
|
||||||
|
$response = $kernel->handle($request);
|
||||||
|
$response->send();
|
||||||
|
$kernel->terminate($request, $response);
|
269
src/Command/GenerateCommand.php
Normal file
269
src/Command/GenerateCommand.php
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\DataTransferObject\Config;
|
||||||
|
use App\DataTransferObject\TemplateFile;
|
||||||
|
use App\Enum\Language;
|
||||||
|
use App\Enum\WebServer;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||||
|
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||||
|
use Symfony\Component\Serializer\Serializer;
|
||||||
|
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||||
|
use Symfony\Component\Validator\Validation;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Twig\Environment;
|
||||||
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:generate',
|
||||||
|
description: 'Generate project-specific configuration files',
|
||||||
|
)]
|
||||||
|
class GenerateCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addOption(
|
||||||
|
name: 'config-file',
|
||||||
|
shortcut: ['c'],
|
||||||
|
mode: InputOption::VALUE_REQUIRED,
|
||||||
|
description: 'The path to the project\'s build.yaml file',
|
||||||
|
default: 'build.yaml',
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
name: 'output-dir',
|
||||||
|
shortcut: ['o'],
|
||||||
|
mode: InputOption::VALUE_REQUIRED,
|
||||||
|
description: 'The directory to create files in',
|
||||||
|
default: '.',
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
$configFile = $input->getOption(name: 'config-file');
|
||||||
|
$outputDir = $input->getOption(name: 'output-dir');
|
||||||
|
|
||||||
|
$configurationData = array_merge(
|
||||||
|
Yaml::parseFile(filename: __DIR__ . '/../../resources/build.defaults.yaml'),
|
||||||
|
Yaml::parseFile(filename: $configFile),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert the input to a configuration data object.
|
||||||
|
$normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter());
|
||||||
|
$serializer = new Serializer([$normalizer], [new JsonEncoder()]);
|
||||||
|
$configurationDataObject = $serializer->deserialize(json_encode($configurationData), Config::class, 'json');
|
||||||
|
|
||||||
|
$validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();
|
||||||
|
$violations = $validator->validate($configurationDataObject);
|
||||||
|
|
||||||
|
if (0 < $violations->count()) {
|
||||||
|
$io->error('Configuration is invalid.');
|
||||||
|
|
||||||
|
$io->listing(
|
||||||
|
collect($violations)
|
||||||
|
->map(fn (ConstraintViolationInterface $v) => "{$v->getPropertyPath()} - {$v->getMessage()}")
|
||||||
|
->toArray()
|
||||||
|
);
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($configurationData['docker-compose'])) {
|
||||||
|
$configurationData['dockerCompose'] = $configurationData['docker-compose'];
|
||||||
|
$configurationData['docker-compose'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$configurationData['managedText'] = 'Do not edit this file. It is automatically generated by https://www.oliverdavies.uk/build-configs.';
|
||||||
|
|
||||||
|
$filesToGenerate = $this->getFiles(configurationData: $configurationData);
|
||||||
|
|
||||||
|
$io->info("Building configuration for {$configurationData['name']}.");
|
||||||
|
|
||||||
|
$io->write('Generated files:');
|
||||||
|
$io->listing($this->getListOfFiles(filesToGenerate: $filesToGenerate)->toArray());
|
||||||
|
|
||||||
|
$this->generateFiles(
|
||||||
|
configurationData: $configurationData,
|
||||||
|
filesToGenerate: $filesToGenerate,
|
||||||
|
outputDir: $outputDir,
|
||||||
|
);
|
||||||
|
//
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string> $configurationData
|
||||||
|
* @param Collection<int, TemplateFile> $filesToGenerate
|
||||||
|
*/
|
||||||
|
function generateFiles(
|
||||||
|
Collection $filesToGenerate,
|
||||||
|
string $outputDir,
|
||||||
|
array $configurationData,
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$filesystem = new Filesystem();
|
||||||
|
$twig = new Environment(new FilesystemLoader([__DIR__ . '/../../templates']));
|
||||||
|
|
||||||
|
$filesToGenerate->each(function(TemplateFile $templateFile) use ($configurationData, $filesystem, $outputDir, $twig): void {
|
||||||
|
if ($templateFile->path !== null) {
|
||||||
|
if (!$filesystem->exists($templateFile->path)) {
|
||||||
|
$filesystem->mkdir("{$outputDir}/{$templateFile->path}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sourceFile = "{$templateFile->data}.twig";
|
||||||
|
$outputFile = collect([
|
||||||
|
$outputDir,
|
||||||
|
$templateFile->path,
|
||||||
|
$templateFile->name,
|
||||||
|
])->filter()->implode('/');
|
||||||
|
|
||||||
|
$filesystem->dumpFile($outputFile, $twig->render($sourceFile, $configurationData));
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the Docker entrypoint file is generated, ensure it is executable.
|
||||||
|
if ($filesystem->exists("{$outputDir}/tools/docker/images/php/root/usr/local/bin/docker-entrypoint-php")) {
|
||||||
|
$filesystem->chmod("{$outputDir}/tools/docker/images/php/root/usr/local/bin/docker-entrypoint-php", 0755);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFiles(array $configurationData): Collection
|
||||||
|
{
|
||||||
|
/** @var Collection<int, TemplateFile> */
|
||||||
|
$filesToGenerate = collect([
|
||||||
|
new TemplateFile(data: 'common/.dockerignore', name: '.dockerignore'),
|
||||||
|
new TemplateFile(data: 'common/.hadolint.yaml', name: '.hadolint.yaml'),
|
||||||
|
new TemplateFile(data: 'env.example', name: '.env.example'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraDatabases = Arr::get($configurationData, 'database.extra_databases', []);
|
||||||
|
if (count($extraDatabases) > 0) {
|
||||||
|
$filesToGenerate[] = new TemplateFile(
|
||||||
|
data: 'extra-databases.sql',
|
||||||
|
name: 'extra-databases.sql',
|
||||||
|
path: 'tools/docker/images/database/root/docker-entrypoint-initdb.d',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false !== Arr::get($configurationData, "justfile", true)) {
|
||||||
|
$filesToGenerate[] = new TemplateFile(data: 'justfile', name: 'justfile');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($configurationData['dockerCompose']) && $configurationData['dockerCompose'] !== null) {
|
||||||
|
$filesToGenerate[] = new TemplateFile(data: 'docker-compose.yaml', name: 'docker-compose.yaml');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static::isPhp(Arr::get($configurationData, 'language'))) {
|
||||||
|
$filesToGenerate[] = new TemplateFile(data: 'php/Dockerfile', name: 'Dockerfile');
|
||||||
|
$filesToGenerate[] = new TemplateFile(data: 'php/phpcs.xml', name: 'phpcs.xml.dist');
|
||||||
|
$filesToGenerate[] = new TemplateFile(data: 'php/phpunit.xml', name: 'phpunit.xml.dist');
|
||||||
|
$filesToGenerate[] = new TemplateFile(
|
||||||
|
data: 'php/docker-entrypoint-php',
|
||||||
|
name: 'docker-entrypoint-php',
|
||||||
|
path: 'tools/docker/images/php/root/usr/local/bin',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Arr::has(array: $configurationData, keys: 'php.phpstan')) {
|
||||||
|
$filesToGenerate[] = new TemplateFile(data: 'php/phpstan.neon', name: 'phpstan.neon.dist');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static::isNode(Arr::get($configurationData, 'language'))) {
|
||||||
|
$filesToGenerate[] = new TemplateFile(data: 'node/.yarnrc', name: '.yarnrc');
|
||||||
|
$filesToGenerate[] = new TemplateFile(data: 'node/Dockerfile', name: 'Dockerfile');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static::isCaddy(Arr::get($configurationData, 'web.type'))) {
|
||||||
|
$filesToGenerate[] = new TemplateFile(
|
||||||
|
data: 'web/caddy/Caddyfile',
|
||||||
|
name: 'Caddyfile',
|
||||||
|
path: 'tools/docker/images/web/root/etc/caddy',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static::isNginx(Arr::get($configurationData, 'web.type'))) {
|
||||||
|
$filesToGenerate[] = new TemplateFile(
|
||||||
|
data: 'web/nginx/default.conf',
|
||||||
|
name: 'default.conf',
|
||||||
|
path: 'tools/docker/images/web/root/etc/nginx/conf.d',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('drupal-project' === Arr::get($configurationData, 'type')) {
|
||||||
|
// Add a Drupal version of phpunit.xml.dist.
|
||||||
|
$filesToGenerate[] = new TemplateFile(data: 'drupal-project/phpunit.xml.dist', name: 'phpunit.xml.dist');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Arr::get($configurationData, 'experimental.createGitHubActionsConfiguration', false) === true) {
|
||||||
|
$filesToGenerate[] = new TemplateFile(
|
||||||
|
data: 'ci/github-actions/ci.yml',
|
||||||
|
name: 'ci.yml',
|
||||||
|
path: '.github/workflows',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filesToGenerate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getListOfFiles(Collection $filesToGenerate): Collection
|
||||||
|
{
|
||||||
|
return $filesToGenerate
|
||||||
|
->map(fn (TemplateFile $templateFile): string =>
|
||||||
|
collect([$templateFile->path, $templateFile->name])->filter()->implode('/'))
|
||||||
|
->unique()
|
||||||
|
->sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCaddy(?string $webServer): bool
|
||||||
|
{
|
||||||
|
if (is_null($webServer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtoupper($webServer) === WebServer::CADDY->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNginx(?string $webServer): bool
|
||||||
|
{
|
||||||
|
if (is_null($webServer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtoupper($webServer) === WebServer::NGINX->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNode(?string $language): bool
|
||||||
|
{
|
||||||
|
if (is_null($language)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtoupper($language) === Language::NODE->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPhp(?string $language): bool
|
||||||
|
{
|
||||||
|
if (is_null($language)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtoupper($language) === Language::PHP->name;
|
||||||
|
}
|
||||||
|
}
|
49
src/Command/InitCommand.php
Normal file
49
src/Command/InitCommand.php
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
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\Output\OutputInterface;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:init',
|
||||||
|
description: 'Add a short description for your command',
|
||||||
|
)]
|
||||||
|
class InitCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addArgument('projectName', InputArgument::REQUIRED, 'The name of the project')
|
||||||
|
->addArgument('language', InputArgument::REQUIRED, 'The language the project uses')
|
||||||
|
->addArgument('type', InputArgument::REQUIRED, 'The type of project')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
[
|
||||||
|
'language' => $language,
|
||||||
|
'projectName' => $projectName,
|
||||||
|
'type' => $type,
|
||||||
|
] = $input->getArguments();
|
||||||
|
|
||||||
|
$projectName = str_replace('.', '-', $projectName);
|
||||||
|
|
||||||
|
// TODO: validate the project type.
|
||||||
|
$output = <<<EOF
|
||||||
|
name: $projectName
|
||||||
|
language: $language
|
||||||
|
type: $type
|
||||||
|
EOF;
|
||||||
|
|
||||||
|
file_put_contents('build.yaml', $output);
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
0
src/Controller/.gitignore
vendored
Normal file
0
src/Controller/.gitignore
vendored
Normal file
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace OliverDaviesLtd\BuildConfigs;
|
namespace App\DataTransferObject;
|
||||||
|
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
final class ConfigurationData
|
final class Config
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var array<string,string|integer|array<int,string>>
|
* @var array<string,string|integer|array<int,string>>
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace OliverDaviesLtd\BuildConfigs\DataTransferObject;
|
namespace App\DataTransferObject;
|
||||||
|
|
||||||
readonly final class TemplateFile
|
readonly final class TemplateFile
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace OliverDaviesLtd\BuildConfigs\Enum;
|
namespace App\Enum;
|
||||||
|
|
||||||
enum Language
|
enum Language
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace OliverDaviesLtd\BuildConfigs\Enum;
|
namespace App\Enum;
|
||||||
|
|
||||||
enum WebServer
|
enum WebServer
|
||||||
{
|
{
|
||||||
|
|
56
src/Kernel.php
Normal file
56
src/Kernel.php
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the box project.
|
||||||
|
*
|
||||||
|
* (c) Kevin Herrera <kevin@herrera.io>
|
||||||
|
* Théo Fidry <theo.fidry@gmail.com>
|
||||||
|
*
|
||||||
|
* This source file is subject to the MIT license that is bundled
|
||||||
|
* with this source code in the file LICENSE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||||
|
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||||
|
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||||
|
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
|
||||||
|
use function dirname;
|
||||||
|
|
||||||
|
class Kernel extends BaseKernel
|
||||||
|
{
|
||||||
|
use MicroKernelTrait;
|
||||||
|
|
||||||
|
protected function configureContainer(ContainerConfigurator $container): void
|
||||||
|
{
|
||||||
|
$container->import('../config/{packages}/*.yaml');
|
||||||
|
$container->import('../config/{packages}/'.$this->environment.'/*.yaml');
|
||||||
|
|
||||||
|
if (is_file(dirname(__DIR__).'/config/services.yaml')) {
|
||||||
|
$container->import('../config/services.yaml');
|
||||||
|
$container->import('../config/{services}_'.$this->environment.'.yaml');
|
||||||
|
} elseif (is_file($path = dirname(__DIR__).'/config/services.php')) {
|
||||||
|
(require $path)($container->withPath($path), $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configureRoutes(RoutingConfigurator $routes): void
|
||||||
|
{
|
||||||
|
$routes->import('../config/{routes}/'.$this->environment.'/*.yaml');
|
||||||
|
$routes->import('../config/{routes}/*.yaml');
|
||||||
|
|
||||||
|
if (is_file(dirname(__DIR__).'/config/routes.yaml')) {
|
||||||
|
$routes->import('../config/routes.yaml');
|
||||||
|
} elseif (is_file($path = dirname(__DIR__).'/config/routes.php')) {
|
||||||
|
(require $path)($routes->withPath($path), $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProjectDir(): string
|
||||||
|
{
|
||||||
|
return __DIR__.'/../';
|
||||||
|
}
|
||||||
|
}
|
218
symfony.lock
Normal file
218
symfony.lock
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
{
|
||||||
|
"doctrine/annotations": {
|
||||||
|
"version": "2.0",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.10",
|
||||||
|
"ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"php": {
|
||||||
|
"version": "7.2.9"
|
||||||
|
},
|
||||||
|
"psr/cache": {
|
||||||
|
"version": "1.0.1"
|
||||||
|
},
|
||||||
|
"psr/container": {
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"psr/event-dispatcher": {
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"psr/log": {
|
||||||
|
"version": "1.1.3"
|
||||||
|
},
|
||||||
|
"psr/simple-cache": {
|
||||||
|
"version": "1.0.1"
|
||||||
|
},
|
||||||
|
"symfony/cache": {
|
||||||
|
"version": "v5.0.11"
|
||||||
|
},
|
||||||
|
"symfony/cache-contracts": {
|
||||||
|
"version": "v2.2.0"
|
||||||
|
},
|
||||||
|
"symfony/config": {
|
||||||
|
"version": "v5.0.11"
|
||||||
|
},
|
||||||
|
"symfony/console": {
|
||||||
|
"version": "5.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "master",
|
||||||
|
"version": "5.1",
|
||||||
|
"ref": "c6d02bdfba9da13c22157520e32a602dbee8a75c"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin/console"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/contracts": {
|
||||||
|
"version": "v1.0.2"
|
||||||
|
},
|
||||||
|
"symfony/debug": {
|
||||||
|
"version": "v4.2.4"
|
||||||
|
},
|
||||||
|
"symfony/dependency-injection": {
|
||||||
|
"version": "v5.0.11"
|
||||||
|
},
|
||||||
|
"symfony/deprecation-contracts": {
|
||||||
|
"version": "v2.2.0"
|
||||||
|
},
|
||||||
|
"symfony/dotenv": {
|
||||||
|
"version": "v5.0.11"
|
||||||
|
},
|
||||||
|
"symfony/error-handler": {
|
||||||
|
"version": "v5.1.5"
|
||||||
|
},
|
||||||
|
"symfony/event-dispatcher": {
|
||||||
|
"version": "v5.1.5"
|
||||||
|
},
|
||||||
|
"symfony/event-dispatcher-contracts": {
|
||||||
|
"version": "v2.2.0"
|
||||||
|
},
|
||||||
|
"symfony/filesystem": {
|
||||||
|
"version": "v5.0.11"
|
||||||
|
},
|
||||||
|
"symfony/finder": {
|
||||||
|
"version": "v5.0.11"
|
||||||
|
},
|
||||||
|
"symfony/flex": {
|
||||||
|
"version": "1.0",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "master",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "c0eeb50665f0f77226616b6038a9b06c03752d8e"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
".env"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/framework-bundle": {
|
||||||
|
"version": "5.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "master",
|
||||||
|
"version": "5.2",
|
||||||
|
"ref": "6ec87563dcc85cd0c48856dcfbfc29610506d250"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/cache.yaml",
|
||||||
|
"config/packages/framework.yaml",
|
||||||
|
"config/packages/test/framework.yaml",
|
||||||
|
"config/preload.php",
|
||||||
|
"config/routes/dev/framework.yaml",
|
||||||
|
"config/services.yaml",
|
||||||
|
"public/index.php",
|
||||||
|
"src/Controller/.gitignore",
|
||||||
|
"src/Kernel.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/http-client-contracts": {
|
||||||
|
"version": "v2.3.1"
|
||||||
|
},
|
||||||
|
"symfony/http-foundation": {
|
||||||
|
"version": "v5.1.5"
|
||||||
|
},
|
||||||
|
"symfony/http-kernel": {
|
||||||
|
"version": "v5.1.5"
|
||||||
|
},
|
||||||
|
"symfony/maker-bundle": {
|
||||||
|
"version": "1.48",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"symfony/polyfill-intl-grapheme": {
|
||||||
|
"version": "v1.22.1"
|
||||||
|
},
|
||||||
|
"symfony/polyfill-intl-normalizer": {
|
||||||
|
"version": "v1.22.1"
|
||||||
|
},
|
||||||
|
"symfony/polyfill-mbstring": {
|
||||||
|
"version": "v1.20.0"
|
||||||
|
},
|
||||||
|
"symfony/polyfill-php73": {
|
||||||
|
"version": "v1.20.0"
|
||||||
|
},
|
||||||
|
"symfony/polyfill-php80": {
|
||||||
|
"version": "v1.20.0"
|
||||||
|
},
|
||||||
|
"symfony/polyfill-php81": {
|
||||||
|
"version": "v1.24.0"
|
||||||
|
},
|
||||||
|
"symfony/routing": {
|
||||||
|
"version": "5.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "master",
|
||||||
|
"version": "5.1",
|
||||||
|
"ref": "b4f3e7c95e38b606eef467e8a42a8408fc460c43"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/prod/routing.yaml",
|
||||||
|
"config/packages/routing.yaml",
|
||||||
|
"config/routes.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/service-contracts": {
|
||||||
|
"version": "v2.2.0"
|
||||||
|
},
|
||||||
|
"symfony/string": {
|
||||||
|
"version": "v5.2.6"
|
||||||
|
},
|
||||||
|
"symfony/translation": {
|
||||||
|
"version": "6.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "5.3",
|
||||||
|
"ref": "da64f5a2b6d96f5dc24914517c0350a5f91dee43"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/translation.yaml",
|
||||||
|
"translations/.gitignore"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/twig-bundle": {
|
||||||
|
"version": "6.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "5.4",
|
||||||
|
"ref": "bb2178c57eee79e6be0b297aa96fc0c0def81387"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/twig.yaml",
|
||||||
|
"templates/base.html.twig"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/validator": {
|
||||||
|
"version": "6.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "5.3",
|
||||||
|
"ref": "c32cfd98f714894c4f128bb99aa2530c1227603c"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/validator.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/var-dumper": {
|
||||||
|
"version": "v5.1.5"
|
||||||
|
},
|
||||||
|
"symfony/var-exporter": {
|
||||||
|
"version": "v5.0.11"
|
||||||
|
},
|
||||||
|
"symfony/yaml": {
|
||||||
|
"version": "v5.0.11"
|
||||||
|
},
|
||||||
|
"twig/extra-bundle": {
|
||||||
|
"version": "v3.5.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,45 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Test Case
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The closure you provide to your test functions is always bound to a specific PHPUnit test
|
|
||||||
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
|
|
||||||
| need to change it using the "uses()" function to bind a different classes or traits.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
// uses(Tests\TestCase::class)->in('Feature');
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Expectations
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| When you're writing tests, you often need to check that values meet certain conditions. The
|
|
||||||
| "expect()" function gives you access to a set of "expectations" methods that you can use
|
|
||||||
| to assert different things. Of course, you may extend the Expectation API at any time.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
expect()->extend('toBeOne', function () {
|
|
||||||
return $this->toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Functions
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
|
|
||||||
| project that you don't want to repeat in every file. Here you can also expose helpers as
|
|
||||||
| global functions to help you to reduce the number of lines of code in your test files.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
function something()
|
|
||||||
{
|
|
||||||
// ..
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use OliverDaviesLtd\BuildConfigs\Validator\ConfigurationValidator;
|
|
||||||
|
|
||||||
beforeEach(function (): void {
|
|
||||||
$this->validator = new ConfigurationValidator();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('The project name should be a string', function (mixed $projectName, int $expectedViolationCount) {
|
|
||||||
$configuration = [
|
|
||||||
'name' => $projectName,
|
|
||||||
];
|
|
||||||
|
|
||||||
expect($this->validator->validate($configuration))
|
|
||||||
->toHaveCount($expectedViolationCount);
|
|
||||||
})->with(function () {
|
|
||||||
yield 'Non-empty string' => ['test', 0];
|
|
||||||
yield 'Empty string' => ['', 1];
|
|
||||||
yield 'Integer' => [1, 1];
|
|
||||||
yield 'Null' => [null, 1];
|
|
||||||
yield 'True' => [true, 1];
|
|
||||||
yield 'False' => [false, 2];
|
|
||||||
});
|
|
||||||
|
|
||||||
test('The project language should be a supported language', function (mixed $language, int $expectedViolationCount) {
|
|
||||||
$configuration = [
|
|
||||||
'language' => $language,
|
|
||||||
];
|
|
||||||
|
|
||||||
expect($this->validator->validate($configuration))
|
|
||||||
->toHaveCount($expectedViolationCount);
|
|
||||||
})->with(function () {
|
|
||||||
yield 'Supported language string' => ['php', 0];
|
|
||||||
yield 'Non-supported language string' => ['not-supported', 1];
|
|
||||||
yield 'Empty string' => ['', 1];
|
|
||||||
yield 'Integer' => [1, 2];
|
|
||||||
yield 'Null' => [null, 1];
|
|
||||||
yield 'True' => [true, 2];
|
|
||||||
yield 'False' => [false, 2];
|
|
||||||
});
|
|
0
translations/.gitignore
vendored
Normal file
0
translations/.gitignore
vendored
Normal file
5
vendor-bin/box/composer.json
Normal file
5
vendor-bin/box/composer.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"require-dev": {
|
||||||
|
"humbug/box": "^4.3"
|
||||||
|
}
|
||||||
|
}
|
3104
vendor-bin/box/composer.lock
generated
Normal file
3104
vendor-bin/box/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue