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'] ?? []));
}