diff --git a/docs/book/index.md b/docs/book/index.md index 5acc1e7..d54a592 100644 --- a/docs/book/index.md +++ b/docs/book/index.md @@ -159,23 +159,25 @@ will need to have a line that instantiates either a Configuration providers are added at the **top** of the `ConfigManager`/`ConfigAggregator` provider array. -## Whitelisting packages to autoinstall +## Marking packages to auto-install At the root package level, you can indicate that certain packages that supply config providers and/or modules should automatically inject configuration, -instead of prompting for installation, via the `component-whitelist` setting. +instead of prompting for installation, via the `component-auto-installs` setting. This value should be an array of package names. ```json -"extra": { +{ + "extra": { "laminas": { - "component-whitelist": [ - "mezzio/mezzio", - "mezzio/mezzio-helper", - "mezzio/mezzio-fastrouterouter", - "mezzio/mezzio-platesrenderer" - ] + "component-auto-installs": [ + "mezzio/mezzio", + "mezzio/mezzio-helper", + "mezzio/mezzio-fastrouterouter", + "mezzio/mezzio-platesrenderer" + ] } + } } ``` diff --git a/src/ComponentInstaller.php b/src/ComponentInstaller.php index 9fca80a..da41df4 100644 --- a/src/ComponentInstaller.php +++ b/src/ComponentInstaller.php @@ -92,7 +92,8 @@ * @internal * * @psalm-type ComposerExtraComponentInstallerProjectArrayType = array{ - * component-whitelist?: non-empty-list + * component-whitelist?: non-empty-list, + * component-auto-installs?: non-empty-list * } * @psalm-type ComposerExtraComponentInstallerArrayType = array{ * component?:non-empty-array, @@ -495,22 +496,22 @@ private function marshalInstallableComponents(array $extra, Collection $options) * @param Collection $options * @param InjectorInterface::TYPE_* $packageType * @param non-empty-string $packageName - * @param list $whitelist + * @param list $autoInstallations */ private function promptForConfigOption( string $name, Collection $options, int $packageType, string $packageName, - array $whitelist, + array $autoInstallations, bool $requireDev = false ): InjectorInterface { if ($cachedInjector = $this->getCachedInjector($packageType)) { return $cachedInjector; } - // If package is whitelisted, don't ask... - if (in_array($packageName, $whitelist, true)) { + // If package is allowed to be auto-installed, don't ask... + if (in_array($packageName, $autoInstallations, true)) { $injector = $options->get(1)->getInjector(); if ($requireDev && $options->has(2)) { return $options->get(2)->getInjector(); @@ -948,9 +949,11 @@ private function addPackageToConfig( // Get extra from root package /** @var array $rootPackageExtra */ - $rootPackageExtra = $this->composer->getPackage()->getExtra(); - $rootExtra = $this->getExtraMetadata($rootPackageExtra, true); - $whitelist = $rootExtra['component-whitelist'] ?? []; + $rootPackageExtra = $this->composer->getPackage()->getExtra(); + $rootExtra = $this->getExtraMetadata($rootPackageExtra, true); + $autoInstallations = $rootExtra['component-auto-installs'] + ?? $rootExtra['component-whitelist'] + ?? []; $this->marshalInstallableComponents($extra, $options) // Create injectors @@ -961,7 +964,7 @@ private function addPackageToConfig( $requireDev, $dependencies, $applicationModules, - $whitelist + $autoInstallations ): void { $packageType = $packageTypes->get($component); @@ -970,7 +973,7 @@ private function addPackageToConfig( $options, $packageType, $name, - $whitelist, + $autoInstallations, $requireDev ); @@ -1056,6 +1059,16 @@ private function filterComponentInstallerSpecificValuesFromComposerExtra( = $maybeLaminasSpecificConfiguration['component-whitelist']; } + if ( + isset($maybeLaminasSpecificConfiguration['component-auto-installs']) + && $this->isNonEmptyListContainingNonEmptyStrings( + $maybeLaminasSpecificConfiguration['component-auto-installs'] + ) + ) { + $laminasSpecificConfiguration['component-auto-installs'] + = $maybeLaminasSpecificConfiguration['component-auto-installs']; + } + return $laminasSpecificConfiguration; } diff --git a/test/ComponentInstallerTest.php b/test/ComponentInstallerTest.php index a59bf5c..fd0b614 100644 --- a/test/ComponentInstallerTest.php +++ b/test/ComponentInstallerTest.php @@ -62,7 +62,7 @@ final class ComponentInstallerTest extends TestCase /** @var InstallationManager&MockObject */ private $installationManager; - /** @var array{laminas?:array{component-whitelist?:list}} */ + /** @var array{laminas?:array{component-auto-installs?:list,component-whitelist?:list}} */ private $rootPackageExtra = []; protected function setUp(): void @@ -949,45 +949,6 @@ public function testOnPostPackageInstallDoesNotPromptIfPackageIsAlreadyInConfigu self::assertStringContainsString("'Some\Component'", $config); } - public function testOnPostPackageInstallDoesNotPromptForWhitelistedPackages(): void - { - $this->createApplicationConfig(); - - $package = $this->createMock(PackageInterface::class); - $package->method('getName')->willReturn('some/component'); - $package->method('getExtra')->willReturn([ - 'laminas' => [ - 'component' => 'Some\\Component', - ], - ]); - - $operation = $this->createMock(InstallOperation::class); - $operation->method('getPackage')->willReturn($package); - - $event = $this->createMock(PackageEvent::class); - $event->method('isDevMode')->willReturn(true); - $event->method('getOperation')->willReturn($operation); - $this->prepareEventForPackageProviderDetection($event, 'some/component'); - - $this->rootPackage->method('getName')->willReturn('some/component'); - $this->rootPackageExtra = [ - 'laminas' => [ - 'component-whitelist' => ['some/component'], - ], - ]; - - $this->createOutputAssertions([ - 'Installing Some\Component from package some/component', - ]); - $this->io - ->expects(self::never()) - ->method('ask'); - - $this->installer->onPostPackageInstall($event); - $config = file_get_contents(vfsStream::url('project/config/application.config.php')); - self::assertStringContainsString("'Some\Component'", $config); - } - public function testOnPostPackageInstallPromptsForConfigOptions(): void { $this->createApplicationConfig(); @@ -1603,7 +1564,178 @@ public function testUninstallMessageWithDifferentInjectors( $this->installer->onPostPackageUninstall($event); } - public function testInstallWhitelistedDevModuleWithDifferentInjectors(): void + public function testInstallAutoInstallableDevModuleWithDifferentInjectors(): void + { + $moduleConfigContent = <<<'CONFIG' + [ + 'Laminas\Router', + 'Laminas\Validator', + 'Application' + ] +]; +CONFIG; + + $this->createConfigFile('modules.config.php', $moduleConfigContent); + + $configContents = <<<'CONFIG' + [ + ] +]; +CONFIG; + foreach (['development.config.php.dist', 'development.config.php'] as $configName) { + $this->createConfigFile($configName, $configContents); + } + + $this->rootPackage->method('getDevRequires')->willReturn(['some/component' => '*']); + $this->rootPackageExtra = [ + 'laminas' => [ + "component-auto-installs" => [ + "some/component", + ], + ], + ]; + + $package = $this->createMock(PackageInterface::class); + $package->method('getName')->willReturn('some/component'); + $package->method('getExtra')->willReturn([ + 'laminas' => [ + 'component' => [ + 'Some\\Component', + ], + ], + ]); + + $operation = $this->createMock(InstallOperation::class); + $operation->method('getPackage')->willReturn($package); + + $event = $this->createMock(PackageEvent::class); + $event->method('isDevMode')->willReturn(true); + $event->method('getOperation')->willReturn($operation); + $this->prepareEventForPackageProviderDetection($event, 'some/component'); + + $this->installer->onPostPackageInstall($event); + /** + * @psalm-suppress UnresolvableInclude + * @psalm-var array{modules:list} $config + */ + $config = require vfsStream::url('project/config/modules.config.php'); + $modules = $config['modules']; + self::assertEquals([ + 'Laminas\Router', + 'Laminas\Validator', + 'Application', + ], $modules); + /** + * @psalm-suppress UnresolvableInclude + * @psalm-var array{modules:list} $config + */ + $config = require vfsStream::url('project/config/development.config.php'); + $modules = $config['modules']; + self::assertEquals(['Some\Component'], $modules); + } + + public function testOnPostPackageInstallDoesNotPromptForAutoInstallablePackages(): void + { + $this->createApplicationConfig(); + + $package = $this->createMock(PackageInterface::class); + $package->method('getName')->willReturn('some/component'); + $package->method('getExtra')->willReturn([ + 'laminas' => [ + 'component' => 'Some\\Component', + ], + ]); + + $operation = $this->createMock(InstallOperation::class); + $operation->method('getPackage')->willReturn($package); + + $event = $this->createMock(PackageEvent::class); + $event->method('isDevMode')->willReturn(true); + $event->method('getOperation')->willReturn($operation); + $this->prepareEventForPackageProviderDetection($event, 'some/component'); + + $this->rootPackage->method('getName')->willReturn('some/component'); + $this->rootPackageExtra = [ + 'laminas' => [ + 'component-auto-installs' => ['some/component'], + ], + ]; + + $this->createOutputAssertions([ + 'Installing Some\Component from package some/component', + ]); + $this->io + ->expects(self::never()) + ->method('ask'); + + $this->installer->onPostPackageInstall($event); + $config = file_get_contents(vfsStream::url('project/config/application.config.php')); + self::assertStringContainsString("'Some\Component'", $config); + } + + public function testInstallAutoInstallableDevModuleWithUniqueInjector(): void + { + $moduleConfigContent = <<<'CONFIG' + [ + 'Laminas\Router', + 'Laminas\Validator', + 'Application', + ] +]; +CONFIG; + + $this->createConfigFile('modules.config.php', $moduleConfigContent); + + $this->rootPackageExtra = [ + 'laminas' => [ + "component-auto-installs" => [ + "some/module", + ], + ], + ]; + + $package = $this->createMock(PackageInterface::class); + $package->method('getName')->willReturn('some/module'); + $package->method('getExtra')->willReturn([ + 'laminas' => [ + 'module' => 'Some\\Module', + ], + ]); + + $operation = $this->createMock(InstallOperation::class); + $operation->method('getPackage')->willReturn($package); + + $event = $this->createMock(PackageEvent::class); + $event->method('isDevMode')->willReturn(true); + $event->method('getOperation')->willReturn($operation); + $this->prepareEventForPackageProviderDetection($event, 'some/module'); + + $this->rootPackage + ->method('getName') + ->willReturn('some/module'); + + $this->installer->onPostPackageInstall($event); + /** + * @psalm-suppress UnresolvableInclude + * @psalm-var array{modules:list} $config + */ + $config = require vfsStream::url('project/config/modules.config.php'); + $modules = $config['modules']; + self::assertEquals([ + 'Laminas\Router', + 'Laminas\Validator', + 'Application', + 'Some\Module', + ], $modules); + } + + public function testInstallDeprecatedWhitelistedDevModuleWithDifferentInjectors(): void { $moduleConfigContent = <<<'CONFIG' createApplicationConfig(); + + $package = $this->createMock(PackageInterface::class); + $package->method('getName')->willReturn('some/component'); + $package->method('getExtra')->willReturn([ + 'laminas' => [ + 'component' => 'Some\\Component', + ], + ]); + + $operation = $this->createMock(InstallOperation::class); + $operation->method('getPackage')->willReturn($package); + + $event = $this->createMock(PackageEvent::class); + $event->method('isDevMode')->willReturn(true); + $event->method('getOperation')->willReturn($operation); + $this->prepareEventForPackageProviderDetection($event, 'some/component'); + + $this->rootPackage->method('getName')->willReturn('some/component'); + $this->rootPackageExtra = [ + 'laminas' => [ + 'component-whitelist' => ['some/component'], + ], + ]; + + $this->createOutputAssertions([ + 'Installing Some\Component from package some/component', + ]); + $this->io + ->expects(self::never()) + ->method('ask'); + + $this->installer->onPostPackageInstall($event); + $config = file_get_contents(vfsStream::url('project/config/application.config.php')); + self::assertStringContainsString("'Some\Component'", $config); + } + + public function testInstallDeprecatedWhitelistedDevModuleWithUniqueInjector(): void { $moduleConfigContent = <<<'CONFIG'