Builds the /api Foundation into the base (per api/.memory/foundation-plan.md):
- app/Services/Installer.php DB test + dump.sql import + crypto keys + /api config + lock
- app/Services/Migrator.php versioned db/migrations/*.sql runner (+ migrations table)
- commands/InstallCommand.php (app:install), commands/MigrateCommand.php (db:migrate)
- app/Controllers/{JsonController,PublicController,ApiController} envelope + two-tier auth
- public/controllers/{health,admin}.php GET /api/health (public), /api/admin/ping (bearer)
- db/migrations/001_*.sql smoke migration
- install/controllers/index.php web wizard now delegates to Installer (path bugs fixed, lock)
- console + composer.json register commands / add commands to classmap
- app/src/pages/api-health-test.astro browser round-trip proof page
Verified (no DB): composer install OK; php console lists app:install + db:migrate;
PSR-4 classes autoload; CLI fails gracefully (validation, bad DB, missing config) with
no artifacts left; app builds 14 pages. Live DB + HTTP round-trip pending a served instance.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYHWLHihq3v9nxNwoPCKSn
54 lines
2.4 KiB
PHP
54 lines
2.4 KiB
PHP
<?php
|
|
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 App\Services\Installer;
|
|
|
|
class InstallCommand extends Command
|
|
{
|
|
protected function configure()
|
|
{
|
|
$this->setName('app:install')
|
|
->setDescription('Install the framework: import schema, write config, lock.')
|
|
->addOption('db-host', null, InputOption::VALUE_REQUIRED, 'DB host', 'localhost')
|
|
->addOption('db-name', null, InputOption::VALUE_REQUIRED, 'DB name')
|
|
->addOption('db-user', null, InputOption::VALUE_REQUIRED, 'DB user')
|
|
->addOption('db-pass', null, InputOption::VALUE_REQUIRED, 'DB password')
|
|
->addOption('url', null, InputOption::VALUE_REQUIRED, 'Site URL', 'https://example.com')
|
|
->addOption('name', null, InputOption::VALUE_REQUIRED, 'Project name', 'SeedProject')
|
|
->addOption('force', null, InputOption::VALUE_NONE, 'Re-run even if already installed');
|
|
}
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output)
|
|
{
|
|
$installer = new Installer();
|
|
if ($installer->isInstalled() && !$input->getOption('force')) {
|
|
$output->writeln('<error>Already installed. Use --force to re-run.</error>');
|
|
return Command::FAILURE;
|
|
}
|
|
foreach (['db-name', 'db-user', 'db-pass'] as $req) {
|
|
if (!$input->getOption($req)) {
|
|
$output->writeln("<error>--{$req} is required.</error>");
|
|
return Command::FAILURE;
|
|
}
|
|
}
|
|
try {
|
|
$installer->run([
|
|
'url' => $input->getOption('url'),
|
|
'name' => $input->getOption('name'),
|
|
'db' => [
|
|
'host' => $input->getOption('db-host'),
|
|
'name' => $input->getOption('db-name'),
|
|
'user' => $input->getOption('db-user'),
|
|
'pass' => $input->getOption('db-pass'),
|
|
],
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
$output->writeln('<error>Install failed: ' . $e->getMessage() . '</error>');
|
|
return Command::FAILURE;
|
|
}
|
|
$output->writeln('<info>Install complete. config.php written, schema imported, lock set.</info>');
|
|
return Command::SUCCESS;
|
|
}
|
|
}
|