From 797beceb090f1574f24382a131e1412712952f96 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Mon, 5 Oct 2026 05:20:59 -0400 Subject: [PATCH] Base the install command on package-tools' InstallCommand Decision 6: the install command subclasses laranail/package-tools' InstallCommand and keeps laranail/console's display API through its two traits, so neither package depends on the other. A contract test pins the name, description, visibility, definition, output and exit code against the old base. --- CHANGELOG.md | 8 +++ src/Commands/InstallCommand.php | 37 +++++++++-- src/Providers/PolyglotServiceProvider.php | 4 ++ tests/Feature/InstallCommandContractTest.php | 64 ++++++++++++++++++++ 4 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 tests/Feature/InstallCommandContractTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index fe03dd5..c4fca59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- `laranail::polyglot.install` now extends laranail/package-tools' `InstallCommand` and takes + laranail/console's display API and run lifecycle from its `InteractsWithConsoleServices` and + `InteractsWithConsoleWriter` traits instead of its `Command` base. Name, description, listing + visibility, output and exit code are unchanged and pinned by a new contract test. The command is + bound in the container because the new base takes the `Package` in its constructor. + ### Fixed - **The process-transport CI job still found no tests, and the dist check still named the old diff --git a/src/Commands/InstallCommand.php b/src/Commands/InstallCommand.php index ebb8934..d7db05b 100644 --- a/src/Commands/InstallCommand.php +++ b/src/Commands/InstallCommand.php @@ -4,19 +4,44 @@ namespace Simtabi\Laranail\Polyglot\Commands; -use Simtabi\Laranail\Console\Tools\Commands\Command; -use Simtabi\Laranail\Console\Tools\Commands\Concerns\SupportsNamespacedNames; +use Simtabi\Laranail\Package\Tools\Package; +use Simtabi\Laranail\Console\Tools\Commands\Concerns\InteractsWithConsoleWriter; +use Simtabi\Laranail\Console\Tools\Commands\Concerns\InteractsWithConsoleServices; +use Simtabi\Laranail\Package\Tools\Commands\InstallCommand as PackageToolsInstallCommand; /** * Publish the config and say what to do next. + * + * The base is package-tools' install command, which carries the `::` name support. laranail/console's + * display API (`$this->services`) and managed run lifecycle come from its two traits rather than its + * base class, so neither package has to depend on the other. `handle()` is this command's own: the + * base's generic publish pipeline would print different steps, and the output here is the contract. */ -final class InstallCommand extends Command +final class InstallCommand extends PackageToolsInstallCommand { - use SupportsNamespacedNames; + use InteractsWithConsoleServices; + use InteractsWithConsoleWriter; - protected $name = 'laranail::polyglot.install'; + public const string SIGNATURE = 'laranail::polyglot.install'; - protected $description = 'Publish the laranail/polyglot configuration.'; + public const string DESCRIPTION = 'Publish the laranail/polyglot configuration.'; + + public function __construct(Package $package) + { + // Listed in `php artisan list`, as it always has been: the base hides install commands by + // default, so visibility is passed explicitly rather than inherited. + parent::__construct($package, self::SIGNATURE, hidden: false); + + // The base writes `Install {package}` as the description during construction; restore the + // one this command has always shown. Both the property and Symfony's copy are set, because + // the parent constructor has already pushed the property through setDescription(). + $this->description = self::DESCRIPTION; + $this->setDescription(self::DESCRIPTION); + + // Booted eagerly, as console's own base does, so `$this->services` exists straight after + // construction rather than only once run() has been entered. + $this->bootConsoleSupport(); + } public function handle(): int { diff --git a/src/Providers/PolyglotServiceProvider.php b/src/Providers/PolyglotServiceProvider.php index 9c94ec4..cffac40 100644 --- a/src/Providers/PolyglotServiceProvider.php +++ b/src/Providers/PolyglotServiceProvider.php @@ -62,6 +62,10 @@ public function configurePackage(Package $package): void #[Override] public function packageRegistered(): void { + // The install command's base takes the Package in its constructor, which the container + // cannot supply on its own. + $this->app->bind(InstallCommand::class, fn (): InstallCommand => new InstallCommand($this->package)); + $this->app->singleton( PolyglotConfig::class, static fn (Application $app): PolyglotConfig => new PolyglotConfig($app->make(ConfigRepository::class)), diff --git a/tests/Feature/InstallCommandContractTest.php b/tests/Feature/InstallCommandContractTest.php new file mode 100644 index 0000000..625405b --- /dev/null +++ b/tests/Feature/InstallCommandContractTest.php @@ -0,0 +1,64 @@ +toBeInstanceOf(InstallCommand::class); + + /** @var Command $command */ + return $command; +} + +it('keeps its name, aliases, description and listing visibility', function (): void { + $command = polyglotInstallCommand(); + + expect($command->getName())->toBe('laranail::polyglot.install') + ->and($command->getAliases())->toBe([]) + ->and($command->getDescription())->toBe('Publish the laranail/polyglot configuration.') + ->and($command->isHidden())->toBeFalse(); +}); + +it('takes no options or arguments of its own', function (): void { + $definition = polyglotInstallCommand()->getNativeDefinition(); + + expect($definition->getOptions())->toBe([]) + ->and($definition->getArguments())->toBe([]); +}); + +it('publishes the config and lists the next steps', function (): void { + $this->artisan('laranail::polyglot.install') + ->expectsOutputToContain('Published config/laranail/polyglot.php.') + ->expectsOutputToContain('Next') + ->expectsOutputToContain('Point laranail.polyglot.services.* at your services') + ->expectsOutputToContain('php artisan laranail::polyglot.doctor') + ->expectsOutputToContain('laranail.polyglot.process.enabled is true') + ->expectsOutputToContain('laranail.polyglot.callbacks.enabled is true') + ->assertExitCode(0); +}); + +it('extends the package-tools install base and keeps both console traits', function (): void { + expect(is_subclass_of(InstallCommand::class, PackageToolsInstallCommand::class))->toBeTrue(); + + $traits = class_uses(InstallCommand::class); + + expect($traits)->toHaveKey(InteractsWithConsoleServices::class) + ->and($traits)->toHaveKey(InteractsWithConsoleWriter::class); +});