Skip to content
17 changes: 15 additions & 2 deletions src/Command/Api/ApiCommandHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -532,10 +532,11 @@ private function generateApiListCommands(array $apiCommands, string $commandPref
continue;
}
$namespace = $commandNameParts[1];
if (!array_key_exists($namespace, $apiListCommands)) {
$name = $commandPrefix . ':' . $namespace;
$hasVisibleCommand = $this->namespaceHasHidddenCommand($apiCommands, $namespace);
if (!array_key_exists($name, $apiListCommands) && $hasVisibleCommand) {
/** @var \Acquia\Cli\Command\Acsf\AcsfListCommand|\Acquia\Cli\Command\Api\ApiListCommand $command */
$command = $commandFactory->createListCommand();
$name = $commandPrefix . ':' . $namespace;
$command->setName($name);
$command->setNamespace($name);
$command->setAliases([]);
Expand All @@ -546,6 +547,18 @@ private function generateApiListCommands(array $apiCommands, string $commandPref
return $apiListCommands;
}

/**
* Whether any command in the given namespace is visible (not hidden).
*/
private function namespaceHasHidddenCommand(array $apiCommands, string $namespace): bool
{
// If namespace is in array sites-instance,sites,environment-v3 then only return true if the command is not hidden and the command name starts with the namespace.
if (in_array($namespace, ['sites-instance', 'sites', 'environment-v3'])) {
return false;
}
return true;
}

/**
* @param array<mixed> $requestBody
* @return array<mixed>
Expand Down
8 changes: 8 additions & 0 deletions src/Command/CommandBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,14 @@
$this->setHelp($helpText);
}

/**
* Helper text for IDE-only commands.
*/
public static function getIdeHelperText(): string

Check warning on line 158 in src/Command/CommandBase.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "PublicVisibility": @@ @@ /** * Helper text for IDE-only commands. */ - public static function getIdeHelperText(): string + protected static function getIdeHelperText(): string { return 'This command will only work in an IDE terminal.'; }
{
return 'This command will only work in an IDE terminal.';
}

protected static function getUuidRegexConstraint(): Regex
{
return new Regex([
Expand Down
2 changes: 2 additions & 0 deletions src/Command/Ide/IdePhpVersionCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Acquia\Cli\Command\Ide;

use Acquia\Cli\Command\CommandBase;
use Acquia\Cli\Exception\AcquiaCliException;
use Acquia\DrupalEnvironmentDetector\AcquiaDrupalEnvironmentDetector;
use Symfony\Component\Console\Attribute\AsCommand;
Expand All @@ -22,6 +23,7 @@
$this
->addArgument('version', InputArgument::REQUIRED, 'The PHP version')
->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv());
$this->appendHelp(CommandBase::getIdeHelperText());

Check warning on line 26 in src/Command/Ide/IdePhpVersionCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ protected function configure(): void { $this->addArgument('version', InputArgument::REQUIRED, 'The PHP version')->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv()); - $this->appendHelp(CommandBase::getIdeHelperText()); + } protected function execute(InputInterface $input, OutputInterface $output): int {
}

protected function execute(InputInterface $input, OutputInterface $output): int
Expand Down
2 changes: 2 additions & 0 deletions src/Command/Ide/IdeServiceRestartCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Acquia\Cli\Command\Ide;

use Acquia\Cli\Command\CommandBase;
use Acquia\DrupalEnvironmentDetector\AcquiaDrupalEnvironmentDetector;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
Expand All @@ -25,6 +26,7 @@
->addUsage('apache')
->addUsage('mysql')
->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv());
$this->appendHelp(CommandBase::getIdeHelperText());

Check warning on line 29 in src/Command/Ide/IdeServiceRestartCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ protected function configure(): void { $this->addArgument('service', InputArgument::REQUIRED, 'The name of the service to restart')->addUsage('php')->addUsage('apache')->addUsage('mysql')->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv()); - $this->appendHelp(CommandBase::getIdeHelperText()); + } protected function execute(InputInterface $input, OutputInterface $output): int {
}

protected function execute(InputInterface $input, OutputInterface $output): int
Expand Down
2 changes: 2 additions & 0 deletions src/Command/Ide/IdeServiceStartCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Acquia\Cli\Command\Ide;

use Acquia\Cli\Command\CommandBase;
use Acquia\DrupalEnvironmentDetector\AcquiaDrupalEnvironmentDetector;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
Expand All @@ -25,6 +26,7 @@
->addUsage('apache')
->addUsage('mysql')
->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv());
$this->appendHelp(CommandBase::getIdeHelperText());

Check warning on line 29 in src/Command/Ide/IdeServiceStartCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ protected function configure(): void { $this->addArgument('service', InputArgument::REQUIRED, 'The name of the service to start')->addUsage('php')->addUsage('apache')->addUsage('mysql')->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv()); - $this->appendHelp(CommandBase::getIdeHelperText()); + } protected function execute(InputInterface $input, OutputInterface $output): int {
}

protected function execute(InputInterface $input, OutputInterface $output): int
Expand Down
2 changes: 2 additions & 0 deletions src/Command/Ide/IdeServiceStopCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Acquia\Cli\Command\Ide;

use Acquia\Cli\Command\CommandBase;
use Acquia\DrupalEnvironmentDetector\AcquiaDrupalEnvironmentDetector;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
Expand All @@ -25,6 +26,7 @@
->addUsage('apache')
->addUsage('mysql')
->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv());
$this->appendHelp(CommandBase::getIdeHelperText());

Check warning on line 29 in src/Command/Ide/IdeServiceStopCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ protected function configure(): void { $this->addArgument('service', InputArgument::REQUIRED, 'The name of the service to stop')->addUsage('php')->addUsage('apache')->addUsage('mysql')->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv()); - $this->appendHelp(CommandBase::getIdeHelperText()); + } protected function execute(InputInterface $input, OutputInterface $output): int {
}

protected function execute(InputInterface $input, OutputInterface $output): int
Expand Down
1 change: 1 addition & 0 deletions src/Command/Ide/IdeShareCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
$this
->addOption('regenerate', '', InputOption::VALUE_NONE, 'regenerate the share code')
->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv());
$this->appendHelp(CommandBase::getIdeHelperText());

Check warning on line 29 in src/Command/Ide/IdeShareCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ protected function configure(): void { $this->addOption('regenerate', '', InputOption::VALUE_NONE, 'regenerate the share code')->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv()); - $this->appendHelp(CommandBase::getIdeHelperText()); + } protected function execute(InputInterface $input, OutputInterface $output): int {
}

protected function execute(InputInterface $input, OutputInterface $output): int
Expand Down
2 changes: 2 additions & 0 deletions src/Command/Ide/IdeXdebugToggleCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Acquia\Cli\Command\Ide;

use Acquia\Cli\Command\CommandBase;
use Acquia\Cli\Exception\AcquiaCliException;
use Acquia\DrupalEnvironmentDetector\AcquiaDrupalEnvironmentDetector;
use Symfony\Component\Console\Attribute\AsCommand;
Expand All @@ -20,6 +21,7 @@
{
$this
->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv());
$this->appendHelp(CommandBase::getIdeHelperText());

Check warning on line 24 in src/Command/Ide/IdeXdebugToggleCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ protected function configure(): void { $this->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv()); - $this->appendHelp(CommandBase::getIdeHelperText()); + } protected function execute(InputInterface $input, OutputInterface $output): int {
}

protected function execute(InputInterface $input, OutputInterface $output): int
Expand Down
1 change: 1 addition & 0 deletions src/Command/Ide/Wizard/IdeWizardCreateSshKeyCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
{
$this
->setHidden(!CommandBase::isAcquiaCloudIde());
$this->appendHelp(CommandBase::getIdeHelperText());

Check warning on line 24 in src/Command/Ide/Wizard/IdeWizardCreateSshKeyCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ protected function configure(): void { $this->setHidden(!CommandBase::isAcquiaCloudIde()); - $this->appendHelp(CommandBase::getIdeHelperText()); + } protected function execute(InputInterface $input, OutputInterface $output): int {
}

protected function execute(InputInterface $input, OutputInterface $output): int
Expand Down
1 change: 1 addition & 0 deletions src/Command/Ide/Wizard/IdeWizardDeleteSshKeyCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
{
$this
->setHidden(!CommandBase::isAcquiaCloudIde());
$this->appendHelp(CommandBase::getIdeHelperText());

Check warning on line 26 in src/Command/Ide/Wizard/IdeWizardDeleteSshKeyCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ protected function configure(): void { $this->setHidden(!CommandBase::isAcquiaCloudIde()); - $this->appendHelp(CommandBase::getIdeHelperText()); + } protected function execute(InputInterface $input, OutputInterface $output): int {
}

protected function execute(InputInterface $input, OutputInterface $output): int
Expand Down
14 changes: 13 additions & 1 deletion src/Command/Self/MakeDocsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
#[AsCommand(name: 'self:make-docs', description: 'Generate documentation for all ACLI commands', hidden: true)]
final class MakeDocsCommand extends CommandBase
{
/** Commands excluded from public docs (IDE-only; hidden when not in AH IDE per AcquiaDrupalEnvironmentDetector::isAhIdeEnv()). */
private const DOCS_INCLUDED_COMMANDS = [
'ide:php-version',
'ide:service-restart',
'ide:service-start',
'ide:service-stop',
'ide:share',
'ide:wizard:ssh-key:create-upload',
'ide:wizard:ssh-key:delete',
'ide:xdebug-toggle',
];

protected function configure(): void
{
$this->addOption('format', 'f', InputOption::VALUE_OPTIONAL, 'The format to describe the docs in.', 'rst');
Expand All @@ -43,7 +55,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$commands = json_decode($buffer->fetch(), true);
$index = [];
foreach ($commands['commands'] as $command) {
if ($command['definition']['hidden'] ?? false) {
if (array_key_exists('hidden', $command) && $command['hidden'] && !in_array($command['name'], self::DOCS_INCLUDED_COMMANDS, true)) {
continue;
}
$filename = $command['name'] . '.json';
Expand Down
87 changes: 87 additions & 0 deletions tests/phpunit/src/Commands/Api/ApiCommandHelperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

declare(strict_types=1);

namespace Acquia\Cli\Tests\Commands\Api;

use Acquia\Cli\Command\Api\ApiCommandHelper;
use Acquia\Cli\Command\Api\ApiListCommand;
use Acquia\Cli\Command\CommandBase;
use Acquia\Cli\Tests\CommandTestBase;
use ReflectionMethod;
use Symfony\Component\Console\Command\Command;

/**
* Tests for ApiCommandHelper::generateApiListCommands (via reflection).
* Kills mutations in the namespace visibility and list-command creation logic.
*/
class ApiCommandHelperTest extends CommandTestBase
{
protected function createCommand(): CommandBase
{
return $this->injectCommand(ApiListCommand::class);
}

/**
* Create a mock command with the given name and hidden state.
*/
private function createMockApiCommand(string $name, bool $hidden): Command
{
$cmd = new Command($name);
$cmd->setName($name);
$cmd->setHidden($hidden);
return $cmd;
}

/**
* Call private generateApiListCommands via reflection.
*
* @param Command[] $apiCommands
* @return \Acquia\Cli\Command\Api\ApiListCommandBase[]
*/
private function generateApiListCommands(array $apiCommands, string $commandPrefix = 'api'): array
{
$helper = new ApiCommandHelper($this->logger);
$ref = new ReflectionMethod(ApiCommandHelper::class, 'generateApiListCommands');
return $ref->invoke($helper, $apiCommands, $commandPrefix, $this->getCommandFactory());
}

public function testNamespaceWithVisibleCommandGetsListCommand(): void
{
$apiCommands = [
$this->createMockApiCommand('api:foo:list', true),
$this->createMockApiCommand('api:foo:create', false),
];
$listCommands = $this->generateApiListCommands($apiCommands);
$this->assertArrayHasKey('api:foo', $listCommands);
$this->assertSame('api:foo', $listCommands['api:foo']->getName());
}

/**
* Kill LogicalAndNegation: condition must be (in namespace AND not hidden), not its negation.
* Ensures we only set hasVisibleCommand when we find a command that matches both.
*/
public function testNamespaceWithOneVisibleAndOneHiddenGetsListCommand(): void
{
$apiCommands = [
$this->createMockApiCommand('api:bar:list', false),
$this->createMockApiCommand('api:bar:create', true),
];
$listCommands = $this->generateApiListCommands($apiCommands);
$this->assertArrayHasKey('api:bar', $listCommands);
}

public function testOnlyOneListCommandPerVisibleNamespace(): void
{
$apiCommands = [
$this->createMockApiCommand('api:accounts:list', false),
$this->createMockApiCommand('api:accounts:create', false),
// Excluded namespaces (sites-instance, sites, environment-v3) must not get a list command.
$this->createMockApiCommand('api:sites-instance:list', false),
];
$listCommands = $this->generateApiListCommands($apiCommands);
$this->assertCount(1, $listCommands);
$this->assertArrayHasKey('api:accounts', $listCommands);
$this->assertArrayNotHasKey('api:sites-instance', $listCommands);
}
}
14 changes: 14 additions & 0 deletions tests/phpunit/src/Commands/Self/MakeDocsCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,18 @@ public function testMakeDocsCommandDump(): void
$this->executeCommand(['--dump' => $vfs->url()]);
$this->assertStringContainsString('The completion command dumps', $vfs->getChild('completion.json')->getContent());
}

/**
* Hidden commands must be excluded from dump (not in index, no file).
* When 'hidden' is true the command must be skipped (not in index, no file).
*/
public function testMakeDocsCommandDumpExcludesHiddenCommands(): void
{
$vfs = vfsStream::setup('root');
$this->executeCommand(['--dump' => $vfs->url()]);
$index = json_decode($vfs->getChild('index.json')->getContent(), true);
$commandNames = array_column($index, 'command');
$this->assertNotContains('self:make-docs', $commandNames, 'Hidden commands must be excluded from index');
$this->assertFalse($vfs->hasChild('self:make-docs.json'), 'Hidden commands must not have a doc file');
}
}
Loading