190 lines
5.8 KiB
PHP
190 lines
5.8 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use Phred\Console\Command;
|
|
use Symfony\Component\Console\Input\InputInterface as Input;
|
|
use Symfony\Component\Console\Output\OutputInterface as Output;
|
|
|
|
return new class extends Command {
|
|
protected string $command = 'install';
|
|
protected string $description = 'Scaffold the Phred project structure (idempotent).';
|
|
protected array $options = [
|
|
'--force' => ['mode' => 'flag', 'description' => 'Overwrite existing files when scaffolding.'],
|
|
];
|
|
|
|
public function handle(Input $input, Output $output): int
|
|
{
|
|
$force = (bool) $input->getOption('force');
|
|
|
|
// Define placeholders to keep static analyzers from flagging variables used inside template strings
|
|
/** @var mixed $app */ $app = null;
|
|
/** @var mixed $router */ $router = null;
|
|
|
|
$root = getcwd();
|
|
$dirs = [
|
|
'public',
|
|
'bootstrap',
|
|
'config',
|
|
'routes',
|
|
'modules',
|
|
'resources',
|
|
'storage',
|
|
'storage/logs',
|
|
'storage/cache',
|
|
'storage/sessions',
|
|
'storage/views',
|
|
'storage/uploads',
|
|
'tests',
|
|
'console',
|
|
'console/commands',
|
|
];
|
|
foreach ($dirs as $d) {
|
|
$path = $root . DIRECTORY_SEPARATOR . $d;
|
|
if (!is_dir($path)) {
|
|
@mkdir($path, 0777, true);
|
|
$output->writeln("<info>created</info> $d/");
|
|
}
|
|
}
|
|
|
|
// .gitkeep for empty directories commonly empty
|
|
foreach (['modules', 'resources', 'storage/logs', 'storage/cache', 'storage/sessions', 'storage/views', 'storage/uploads', 'console/commands'] as $maybeEmpty) {
|
|
$file = $root . DIRECTORY_SEPARATOR . $maybeEmpty . DIRECTORY_SEPARATOR . '.gitkeep';
|
|
if (!file_exists($file)) {
|
|
@file_put_contents($file, "");
|
|
}
|
|
}
|
|
|
|
// Files to scaffold
|
|
$files = [
|
|
'public/index.php' => <<<'PHP'
|
|
<?php
|
|
declare(strict_types=1);
|
|
|
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
|
|
|
// Bootstrap the application (container, pipeline, routes)
|
|
$app = require dirname(__DIR__) . '/bootstrap/app.php';
|
|
|
|
// TODO: Build a ServerRequest (Nyholm) and run Relay pipeline
|
|
PHP,
|
|
'bootstrap/app.php' => <<<'PHP'
|
|
<?php
|
|
declare(strict_types=1);
|
|
|
|
use Dotenv\Dotenv;
|
|
|
|
$root = dirname(__DIR__);
|
|
|
|
if (file_exists($root . '/vendor/autoload.php')) {
|
|
require $root . '/vendor/autoload.php';
|
|
}
|
|
|
|
if (file_exists($root . '/.env')) {
|
|
Dotenv::createImmutable($root)->safeLoad();
|
|
}
|
|
|
|
// TODO: Build and return an application kernel/closure
|
|
return static function () {
|
|
return null; // placeholder
|
|
};
|
|
PHP,
|
|
'config/app.php' => <<<'PHP'
|
|
<?php
|
|
declare(strict_types=1);
|
|
|
|
return [
|
|
'name' => getenv('APP_NAME') ?: 'Phred App',
|
|
'env' => getenv('APP_ENV') ?: 'local',
|
|
'debug' => (bool) (getenv('APP_DEBUG') ?: true),
|
|
'timezone' => getenv('APP_TIMEZONE') ?: 'UTC',
|
|
];
|
|
PHP,
|
|
'routes/web.php' => <<<'PHP'
|
|
<?php
|
|
declare(strict_types=1);
|
|
|
|
// Define web routes here
|
|
// Example (FastRoute style):
|
|
// $router->get('/', [HomeController::class, '__invoke']);
|
|
PHP,
|
|
'routes/api.php' => <<<'PHP'
|
|
<?php
|
|
declare(strict_types=1);
|
|
|
|
// Define API routes here
|
|
// Example: $router->get('/health', [HealthController::class, '__invoke']);
|
|
PHP,
|
|
'.env.example' => <<<'ENV'
|
|
APP_NAME=Phred App
|
|
APP_ENV=local
|
|
APP_DEBUG=true
|
|
APP_TIMEZONE=UTC
|
|
|
|
API_FORMAT=rest
|
|
ENV,
|
|
];
|
|
|
|
foreach ($files as $relative => $contents) {
|
|
$path = $root . DIRECTORY_SEPARATOR . $relative;
|
|
if (!file_exists($path) || $force) {
|
|
if (!is_dir(dirname($path))) {
|
|
@mkdir(dirname($path), 0777, true);
|
|
}
|
|
@file_put_contents($path, rtrim($contents) . "\n");
|
|
$output->writeln("<info>wrote</info> $relative");
|
|
}
|
|
}
|
|
|
|
// Copy .env if missing
|
|
if (!file_exists($root . '/.env') && file_exists($root . '/.env.example')) {
|
|
@copy($root . '/.env.example', $root . '/.env');
|
|
$output->writeln('<info>created</info> .env');
|
|
}
|
|
|
|
// Root phred launcher (Unix)
|
|
$launcher = $root . '/phred';
|
|
if (!file_exists($launcher) || $force) {
|
|
$shim = "#!/usr/bin/env php\n<?php\nrequire __DIR__ . '/bin/phred';\n";
|
|
@file_put_contents($launcher, $shim);
|
|
@chmod($launcher, 0755);
|
|
$output->writeln('<info>wrote</info> phred');
|
|
}
|
|
|
|
// Windows launcher
|
|
$launcherWin = $root . '/phred.bat';
|
|
if (!file_exists($launcherWin) || $force) {
|
|
$shimWin = "@ECHO OFF\r\nphp \"%~dp0bin\\phred\" %*\r\n";
|
|
@file_put_contents($launcherWin, $shimWin);
|
|
$output->writeln('<info>wrote</info> phred.bat');
|
|
}
|
|
|
|
// Ensure .gitignore has sensible defaults
|
|
$gitignore = $root . '/.gitignore';
|
|
$giLines = [
|
|
"/vendor/",
|
|
"/.env",
|
|
"/storage/*",
|
|
"!/storage/.gitkeep",
|
|
"/.phpunit.cache",
|
|
"/.php-cs-fixer.cache",
|
|
];
|
|
$existing = file_exists($gitignore) ? file($gitignore, FILE_IGNORE_NEW_LINES) : [];
|
|
$set = $existing ? array_flip($existing) : [];
|
|
$changed = false;
|
|
foreach ($giLines as $line) {
|
|
if (!isset($set[$line])) {
|
|
$existing[] = $line;
|
|
$changed = true;
|
|
}
|
|
}
|
|
if ($changed) {
|
|
@file_put_contents($gitignore, implode("\n", $existing) . "\n");
|
|
$output->writeln('<info>updated</info> .gitignore');
|
|
}
|
|
|
|
$output->writeln("\n<comment>Phred scaffold complete.</comment>");
|
|
$output->writeln("Try: <info>./phred</info> or <info>php bin/phred</info>");
|
|
return 0;
|
|
}
|
|
};
|