From c85825030d5f15cc696aefb9ca8ebb1aaaf886c7 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:13:32 +0100 Subject: [PATCH 1/4] WIP: `cr:list` command --- .../ContentStreamCommandController.php | 10 +++ .../Classes/Command/CrCommandController.php | 75 +++++++++++++++++++ .../Classes/ContentRepositoryRegistry.php | 8 ++ .../Classes/Command/SiteCommandController.php | 2 + 4 files changed, 95 insertions(+) diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/ContentStreamCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/ContentStreamCommandController.php index 6cc56c336ee..bac24262942 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Command/ContentStreamCommandController.php +++ b/Neos.ContentRepositoryRegistry/Classes/Command/ContentStreamCommandController.php @@ -33,6 +33,11 @@ class ContentStreamCommandController extends CommandController */ public function pruneCommand(string $contentRepository = 'default', bool $removeTemporary = false): void { + if (!$this->output->askConfirmation(sprintf('> This operation in "%s" cannot be reverted. Are you sure to proceed? (y/n) ', $contentRepository), false)) { + $this->outputLine('Abort.'); + return; + } + $contentRepositoryId = ContentRepositoryId::fromString($contentRepository); $contentStreamPruner = $this->contentRepositoryRegistry->buildService($contentRepositoryId, new ContentStreamPrunerFactory()); @@ -54,6 +59,11 @@ public function pruneCommand(string $contentRepository = 'default', bool $remove */ public function pruneRemovedFromEventStreamCommand(string $contentRepository = 'default'): void { + if (!$this->output->askConfirmation(sprintf('> This operation in "%s" cannot be reverted. Are you sure to proceed? (y/n) ', $contentRepository), false)) { + $this->outputLine('Abort.'); + return; + } + $contentRepositoryId = ContentRepositoryId::fromString($contentRepository); $contentStreamPruner = $this->contentRepositoryRegistry->buildService($contentRepositoryId, new ContentStreamPrunerFactory()); diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php index 47ab58afc2a..c3704829d7b 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php +++ b/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php @@ -8,13 +8,18 @@ use Neos\ContentRepository\Core\Service\ContentStreamPrunerFactory; use Neos\ContentRepository\Core\Service\WorkspaceMaintenanceServiceFactory; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; +use Neos\ContentRepositoryRegistry\Exception\InvalidConfigurationException; use Neos\ContentRepositoryRegistry\Service\ProjectionReplayServiceFactory; use Neos\EventStore\Model\Event\SequenceNumber; use Neos\EventStore\Model\EventStore\StatusType; use Neos\Flow\Cli\CommandController; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Output\ConsoleOutput; +use Neos\Flow\Persistence\Doctrine\Exception\DatabaseException; +use Neos\Neos\Domain\Model\Site; +use Neos\Neos\Domain\Repository\SiteRepository; use Symfony\Component\Console\Output\Output; final class CrCommandController extends CommandController @@ -23,6 +28,7 @@ final class CrCommandController extends CommandController public function __construct( private readonly ContentRepositoryRegistry $contentRepositoryRegistry, private readonly ProjectionReplayServiceFactory $projectionServiceFactory, + private readonly SiteRepository $siteRepository, ) { parent::__construct(); } @@ -244,4 +250,73 @@ public function pruneCommand(string $contentRepository = 'default', bool $force $this->outputLine('Done.'); } + + public function listCommand() + { + $rows = []; + + /** @var list $sites */ + $sites = []; + try { + $sites = iterator_to_array($this->siteRepository->findAll()); + } catch (DatabaseException) { + // doctrine might have not been migrated yet or no database is connected. + $this->outputLine('Site repository is not accessible.'); + } + + foreach ($this->contentRepositoryRegistry->getContentRepositoryIds() as $contentRepositoryId) { + $contentRepository = null; + try { + $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); + } catch (InvalidConfigurationException $exception) { + $this->outputLine('Content repository %s is not well configures: %s.', [$contentRepositoryId->value, $exception->getMessage()]); + } + + $configuredSites = []; + foreach ($sites as $site) { + if (!$site->getConfiguration()->contentRepositoryId->equals($contentRepositoryId)) { + continue; + } + $configuredSites[] = $site->getName(); + } + + $statusString = '-'; + $workspacesString = '-'; + $contentStreamsString = '-'; + $nodesString = '-'; + + if ($contentRepository) { + $statusString = $contentRepository->status()->isOk() ? 'okay' : 'not okay'; + + try { + $workspacesString = sprintf('%d entries', count($contentRepository->getWorkspaceFinder()->findAll())); + } catch (\Throwable $e) { + $this->outputLine('WorkspaceFinder of %s not functional: %s.', [$contentRepositoryId->value, $e->getMessage()]); + } + + try { + $contentStreamsString = sprintf('%d entries', iterator_count($contentRepository->getContentStreamFinder()->findAllIds())); + } catch (\Throwable $e) { + $this->outputLine('ContentStreamFinder of %s not functional: %s.', [$contentRepositoryId->value, $e->getMessage()]); + } + + try { + $nodesString = sprintf('%d entries', $contentRepository->getContentGraph(WorkspaceName::forLive())->countNodes()); + } catch (\RuntimeException $e) { + $this->outputLine('ContentGraph of %s not functional: %s.', [$contentRepositoryId->value, $e->getMessage()]); + } + } + + $rows[] = [ + $contentRepositoryId->value, + $statusString, + join(', ', $configuredSites) ?: '-', + $workspacesString, + $contentStreamsString, + $nodesString + ]; + } + + $this->output->outputTable($rows, ['Identifier', 'Status', 'Sites', 'Workspaces', 'Contentstreams', 'Nodes']); + } } diff --git a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php index 1fd7cc04910..d14764dc086 100644 --- a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php +++ b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php @@ -81,6 +81,14 @@ public function get(ContentRepositoryId $contentRepositoryId): ContentRepository return $this->getFactory($contentRepositoryId)->getOrBuild(); } + /** + * @return iterable + */ + public function getContentRepositoryIds(): iterable + { + return array_map(ContentRepositoryId::fromString(...), array_keys($this->settings['contentRepositories'] ?? [])); + } + /** * @internal for test cases only */ diff --git a/Neos.Neos/Classes/Command/SiteCommandController.php b/Neos.Neos/Classes/Command/SiteCommandController.php index 06b9dc797fa..9e8aaefb931 100644 --- a/Neos.Neos/Classes/Command/SiteCommandController.php +++ b/Neos.Neos/Classes/Command/SiteCommandController.php @@ -175,6 +175,8 @@ public function listCommand() } } + // todo use outputTable + $this->outputLine(); $this->outputLine(' ' . str_pad('Name', $longestSiteName + 15) . str_pad('Node name', $longestNodeName + 15) From 4cb1889f2dcbfaa13a84c526b0e545421d6e9182 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 22 Jun 2024 11:56:17 +0200 Subject: [PATCH 2/4] WIP: `cr:list` introduce `--verbose` option and show detached site nodes --- .../Classes/Command/CrCommandController.php | 82 ++++++++++++++----- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php index c3704829d7b..cc172ed1667 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php +++ b/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php @@ -8,6 +8,8 @@ use Neos\ContentRepository\Core\Service\ContentStreamPrunerFactory; use Neos\ContentRepository\Core\Service\WorkspaceMaintenanceServiceFactory; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Exception\RootNodeAggregateDoesNotExist; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\ContentRepositoryRegistry\Exception\InvalidConfigurationException; @@ -15,6 +17,7 @@ use Neos\EventStore\Model\Event\SequenceNumber; use Neos\EventStore\Model\EventStore\StatusType; use Neos\Flow\Cli\CommandController; +use Neos\Neos\Domain\Service\NodeTypeNameFactory; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Output\ConsoleOutput; use Neos\Flow\Persistence\Doctrine\Exception\DatabaseException; @@ -251,10 +254,11 @@ public function pruneCommand(string $contentRepository = 'default', bool $force $this->outputLine('Done.'); } - public function listCommand() + /** + * @param bool $verbose shows additional internal output regarding content-streams and nodes in the projection + */ + public function listCommand(bool $verbose = false): void { - $rows = []; - /** @var list $sites */ $sites = []; try { @@ -264,6 +268,7 @@ public function listCommand() $this->outputLine('Site repository is not accessible.'); } + $rows = []; foreach ($this->contentRepositoryRegistry->getContentRepositoryIds() as $contentRepositoryId) { $contentRepository = null; try { @@ -272,12 +277,46 @@ public function listCommand() $this->outputLine('Content repository %s is not well configures: %s.', [$contentRepositoryId->value, $exception->getMessage()]); } + + $liveContentGraph = null; + try { + $liveContentGraph = $contentRepository->getContentGraph(WorkspaceName::forLive()); + } catch (WorkspaceDoesNotExist) { + $this->outputLine('Live workspace in content repository %s not existing.', [$contentRepositoryId->value]); + } + + $siteNodes = []; + // todo wrap in catch runtime exception + if ($liveContentGraph && $verbose) { + $sitesAggregate = null; + try { + $sitesAggregate = $liveContentGraph->findRootNodeAggregateByType(NodeTypeNameFactory::forSites()); + } catch (RootNodeAggregateDoesNotExist) { + $this->outputLine('Sites root node does not exist in content repository %s.', [$contentRepositoryId->value]); + } + + if ($sitesAggregate) { + $siteNodeAggregates = $liveContentGraph->findChildNodeAggregates($sitesAggregate->nodeAggregateId); + foreach ($siteNodeAggregates as $siteNodeAggregate) { + $siteNodes[] = $siteNodeAggregate->nodeName->value; + } + } + } + $configuredSites = []; foreach ($sites as $site) { if (!$site->getConfiguration()->contentRepositoryId->equals($contentRepositoryId)) { continue; } - $configuredSites[] = $site->getName(); + $configuredSites[] = $site->getNodeName()->value; + } + + if ($verbose) { + $connectedSites = array_intersect($configuredSites, $siteNodes); + $unconnectedSiteNodes = array_diff($configuredSites, $siteNodes); + $sitesString = ltrim((join(', ', $connectedSites) . ($unconnectedSiteNodes ? (', (detached: ' . join(', ', $unconnectedSiteNodes) . ')') : '')), ' ,') ?: '-'; + } else { + $sitesString = join(', ', $configuredSites) ?: '-'; } $statusString = '-'; @@ -289,34 +328,37 @@ public function listCommand() $statusString = $contentRepository->status()->isOk() ? 'okay' : 'not okay'; try { - $workspacesString = sprintf('%d entries', count($contentRepository->getWorkspaceFinder()->findAll())); - } catch (\Throwable $e) { + $workspacesString = sprintf('%d found', count($contentRepository->getWorkspaceFinder()->findAll())); + } catch (\RuntimeException $e) { $this->outputLine('WorkspaceFinder of %s not functional: %s.', [$contentRepositoryId->value, $e->getMessage()]); } - try { - $contentStreamsString = sprintf('%d entries', iterator_count($contentRepository->getContentStreamFinder()->findAllIds())); - } catch (\Throwable $e) { - $this->outputLine('ContentStreamFinder of %s not functional: %s.', [$contentRepositoryId->value, $e->getMessage()]); - } - - try { - $nodesString = sprintf('%d entries', $contentRepository->getContentGraph(WorkspaceName::forLive())->countNodes()); - } catch (\RuntimeException $e) { - $this->outputLine('ContentGraph of %s not functional: %s.', [$contentRepositoryId->value, $e->getMessage()]); + if ($verbose) { + try { + $contentStreamsString = sprintf('%d found', iterator_count($contentRepository->getContentStreamFinder()->findAllIds())); + } catch (\RuntimeException $e) { + $this->outputLine('ContentStreamFinder of %s not functional: %s.', [$contentRepositoryId->value, $e->getMessage()]); + } + + try { + if ($liveContentGraph) { + $nodesString = sprintf('%d found', $liveContentGraph->countNodes()); + } + } catch (\RuntimeException $e) { + $this->outputLine('ContentGraph of %s not functional: %s.', [$contentRepositoryId->value, $e->getMessage()]); + } } } $rows[] = [ $contentRepositoryId->value, $statusString, - join(', ', $configuredSites) ?: '-', + $sitesString, $workspacesString, - $contentStreamsString, - $nodesString + ...($verbose ? [$contentStreamsString, $nodesString] : []) ]; } - $this->output->outputTable($rows, ['Identifier', 'Status', 'Sites', 'Workspaces', 'Contentstreams', 'Nodes']); + $this->output->outputTable($rows, ['Identifier', 'Status', 'Sites', 'Workspaces', ...($verbose ? ['Contentstreams', 'Nodes'] : [])]); } } From 694b62140de35d6e709e4de7bc26dd89d7bb3c01 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 22 Jun 2024 12:58:33 +0200 Subject: [PATCH 3/4] WIP: `cr:list` show if only neos site entity or cr node exists in sites column --- .../Classes/Command/CrCommandController.php | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php index cc172ed1667..346b6b25dd0 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php +++ b/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php @@ -259,10 +259,10 @@ public function pruneCommand(string $contentRepository = 'default', bool $force */ public function listCommand(bool $verbose = false): void { - /** @var list $sites */ - $sites = []; + /** @var list $neosSiteEntities */ + $neosSiteEntities = []; try { - $sites = iterator_to_array($this->siteRepository->findAll()); + $neosSiteEntities = iterator_to_array($this->siteRepository->findAll()); } catch (DatabaseException) { // doctrine might have not been migrated yet or no database is connected. $this->outputLine('Site repository is not accessible.'); @@ -285,7 +285,7 @@ public function listCommand(bool $verbose = false): void $this->outputLine('Live workspace in content repository %s not existing.', [$contentRepositoryId->value]); } - $siteNodes = []; + $currenSiteNodes = []; // todo wrap in catch runtime exception if ($liveContentGraph && $verbose) { $sitesAggregate = null; @@ -298,25 +298,30 @@ public function listCommand(bool $verbose = false): void if ($sitesAggregate) { $siteNodeAggregates = $liveContentGraph->findChildNodeAggregates($sitesAggregate->nodeAggregateId); foreach ($siteNodeAggregates as $siteNodeAggregate) { - $siteNodes[] = $siteNodeAggregate->nodeName->value; + $currenSiteNodes[] = $siteNodeAggregate->nodeName->value; } } } - $configuredSites = []; - foreach ($sites as $site) { + $currentNeosSiteEntities = []; + foreach ($neosSiteEntities as $site) { if (!$site->getConfiguration()->contentRepositoryId->equals($contentRepositoryId)) { continue; } - $configuredSites[] = $site->getNodeName()->value; + $currentNeosSiteEntities[] = $site->getNodeName()->value; } if ($verbose) { - $connectedSites = array_intersect($configuredSites, $siteNodes); - $unconnectedSiteNodes = array_diff($configuredSites, $siteNodes); - $sitesString = ltrim((join(', ', $connectedSites) . ($unconnectedSiteNodes ? (', (detached: ' . join(', ', $unconnectedSiteNodes) . ')') : '')), ' ,') ?: '-'; + $connectedWorkingSites = array_intersect($currentNeosSiteEntities, $currenSiteNodes); + $siteNodesWithoutMatchingNeosSiteEntity = array_diff($currenSiteNodes, $currentNeosSiteEntities); + $neosSiteEntitiesWithoutMatchingSiteNode = array_diff($currentNeosSiteEntities, $currenSiteNodes); + $sitesString = ltrim( + (join(', ', $connectedWorkingSites) + . ($siteNodesWithoutMatchingNeosSiteEntity ? (', (only node: ' . join(', ', $siteNodesWithoutMatchingNeosSiteEntity) . ')') : '') + . ($neosSiteEntitiesWithoutMatchingSiteNode ? (', (only neos site: ' . join(', ', $neosSiteEntitiesWithoutMatchingSiteNode) . ')') : '') + ), ' ,') ?: '-'; } else { - $sitesString = join(', ', $configuredSites) ?: '-'; + $sitesString = join(', ', $currentNeosSiteEntities) ?: '-'; } $statusString = '-'; From 742b1232fe50593388e3eb2c518914abdbb91356 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 23 Jun 2024 09:35:33 +0200 Subject: [PATCH 4/4] WIP: Fix phpstan --- .../InternalMethodsNotAllowedOutsideContentRepositoryRule.php | 2 +- .../Classes/Command/CrCommandController.php | 4 +++- .../Classes/ContentRepositoryRegistry.php | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Classes/PhpstanRules/InternalMethodsNotAllowedOutsideContentRepositoryRule.php b/Neos.ContentRepository.BehavioralTests/Classes/PhpstanRules/InternalMethodsNotAllowedOutsideContentRepositoryRule.php index 5dae4e82dfc..4a2417de07f 100644 --- a/Neos.ContentRepository.BehavioralTests/Classes/PhpstanRules/InternalMethodsNotAllowedOutsideContentRepositoryRule.php +++ b/Neos.ContentRepository.BehavioralTests/Classes/PhpstanRules/InternalMethodsNotAllowedOutsideContentRepositoryRule.php @@ -74,7 +74,7 @@ public function processNode(Node $node, Scope $scope): array $targetClassName, $node->name->toString() ) - )->build(), + )->identifier('neos.internal')->build(), ]; } } diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php index 346b6b25dd0..7914b0c72a4 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php +++ b/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php @@ -280,7 +280,7 @@ public function listCommand(bool $verbose = false): void $liveContentGraph = null; try { - $liveContentGraph = $contentRepository->getContentGraph(WorkspaceName::forLive()); + $liveContentGraph = $contentRepository?->getContentGraph(WorkspaceName::forLive()); } catch (WorkspaceDoesNotExist) { $this->outputLine('Live workspace in content repository %s not existing.', [$contentRepositoryId->value]); } @@ -298,6 +298,7 @@ public function listCommand(bool $verbose = false): void if ($sitesAggregate) { $siteNodeAggregates = $liveContentGraph->findChildNodeAggregates($sitesAggregate->nodeAggregateId); foreach ($siteNodeAggregates as $siteNodeAggregate) { + assert($siteNodeAggregate->nodeName !== null); $currenSiteNodes[] = $siteNodeAggregate->nodeName->value; } } @@ -340,6 +341,7 @@ public function listCommand(bool $verbose = false): void if ($verbose) { try { + /** @phpstan-ignore neos.internal */ $contentStreamsString = sprintf('%d found', iterator_count($contentRepository->getContentStreamFinder()->findAllIds())); } catch (\RuntimeException $e) { $this->outputLine('ContentStreamFinder of %s not functional: %s.', [$contentRepositoryId->value, $e->getMessage()]); diff --git a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php index d14764dc086..4a62124b983 100644 --- a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php +++ b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php @@ -86,6 +86,7 @@ public function get(ContentRepositoryId $contentRepositoryId): ContentRepository */ public function getContentRepositoryIds(): iterable { + /** @phpstan-ignore argument.type */ return array_map(ContentRepositoryId::fromString(...), array_keys($this->settings['contentRepositories'] ?? [])); }