arielespinoza07 / console-forge
Framework-agnostic command definition layer mapped to symfony/console
dev-main
2025-08-13 19:41 UTC
Requires
- php: ^8.3
- nunomaduro/termwind: ^2.3
- symfony/console: ^7.3
Requires (Dev)
- laravel/pint: ^1.24
- mockery/mockery: ^1.6
- pestphp/pest: ^3.8
- phpstan/phpstan: ^2.1
Suggests
- nunomaduro/collision: Pretty errors during CLI exceptions
- psr/container: Enable container-based handler resolution
This package is auto-updated.
Last update: 2025-08-14 21:07:09 UTC
README
Framework-agnostic command definition layer mapped to symfony/console, with first-class Termwind integration for a beautiful developer experience.
Requires PHP 8.3+
โจ Features
- Framework-agnostic: works in any PHP project (package, CLI tool, framework).
- Custom command definitions: define commands as invokable classes or closures โ no need to extend
Symfony\Component\Console\Command\Command
. - Simple mapping: a single mapper transforms your definitions into Symfony Console commands.
- First-class Termwind: style your CLI output with HTML-like syntax.
- Optional pretty errors: integrate Collision for nicer exception output.
- DI-friendly: optional PSR-11 container support for handler resolution.
๐ฆ Installation
composer require arielespinoza07/console-forge
๐ Quickstart
- Create a command handler (closure or class) Closure example:
use ConsoleForge\IO; use ConsoleForge\Descriptors\ArgDescriptor; use ConsoleForge\Descriptors\CommandDescriptor; use ConsoleForge\Descriptors\OptDescriptor; use ConsoleForge\Support\Notice\Notice; use ConsoleForge\Support\Notice\TermwindNoticeRenderer; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; $greetCommand = new CommandDescriptor( name: 'greet', description: 'Say hello to someone', args: [ new ArgDescriptor('name', 'Person name') ], opts: [ new OptDescriptor('yell', 'y', 'Uppercase greeting') ], handler: function (InputInterface $input, IO $io, bool $yell = false): void { $name = $input->getArgument('name'); if ($name === null) { (new TermwindNoticeRenderer)->render( notice: Notice\Notice::error( message: 'Argument name is required.', detail: 'Try --help for more information.', ), io: $io, ); return Command::FAILURE; } $msg = $yell ? strtoupper("Hello, $name!") : "Hello, $name!"; (new TermwindNoticeRenderer)->render( notice: Notice::success( message: $msg, ), io: $io, ); return Command::SUCCESS; // exit code } );
Invokable class example:
use ConsoleForge\IO; use ConsoleForge\Support\Notice\Notice; use ConsoleForge\Support\Notice\SymfonyStyleNoticeRenderer; use Symfony\Component\Console\Command\Command; final class CreateUser { public function __invoke(InputInterface $input, IO $io, bool $admin = false): int { $name = $input->getArgument('name'); if ($name === null) { (new SymfonyStyleNoticeRenderer)->render( notice: Notice\Notice::error( message: 'Argument name is required.', detail: 'Try --help for more information.', ), io: $io, ); return Command::FAILURE; } (new SymfonyStyleNoticeRenderer())->render( notice: Notice::success( message: "User {$email} created" . ($admin ? ' as admin' : ''), ), io: $io, ); return Command::SUCCESS; // exit code } }
- Register commands in the registry
use ConsoleForge\Descriptors\ArgDescriptor; use ConsoleForge\Descriptors\CommandDescriptor; use ConsoleForge\Descriptors\OptDescriptor; use ConsoleForge\Registry\InMemoryCommandRegistry; $registry = new InMemoryCommandRegistry(); $registry->add($greetCommand) ->add(new CommandDescriptor( name: 'user:create', description: 'Create a new user', args: [new ArgDescriptor('email', 'User email')], opts: [new OptDescriptor('admin', 'a', 'Make admin')], handler: CreateUser::class, ));
- Map to Symfony Console and run
use ConsoleForge\App; use ConsoleForge\Support\ConfigLoader; // Optional pretty errors (requires nunomaduro/collision) if (class_exists(\NunoMaduro\Collision\Provider::class)) { (new \NunoMaduro\Collision\Provider())->register(); } if (class_exists(ConfigLoader::class)) { // Try loading single-file or directory-based configs. ConfigLoader::loadProjectConfigs($registry, getcwd()); } // Dynamic version if available $version = class_exists(\Composer\InstalledVersions::class) ? (\Composer\InstalledVersions::getPrettyVersion('arielespinoza07/console-forge') ?? 'dev') : 'dev'; $app = App::make( registry: $registry, name: 'ConsoleForge', version: $version, ); $app->run();
๐จ Styling with Termwind
ConsoleForge uses Termwind out of the box. You can style your output with HTML-like syntax:
$io->render("<div class='bg-green-500 text-white p-1'>Success!</div>");
๐งช Testing
composer test
๐ค CONTRIBUTING.md
See CONTRIBUTING for details.