diff --git a/_config/dev.yml b/_config/dev.yml
index c19ab4e1..b87ddecf 100644
--- a/_config/dev.yml
+++ b/_config/dev.yml
@@ -1,19 +1,10 @@
---
Name: graphql-dev
---
-SilverStripe\ORM\DatabaseAdmin:
+SilverStripe\HybridExecution\Command\DbBuild:
extensions:
- - SilverStripe\GraphQL\Extensions\DevBuildExtension
+ - SilverStripe\GraphQL\Extensions\DbBuildExtension
SilverStripe\Dev\DevelopmentAdmin:
- registered_controllers:
- graphql:
- controller: SilverStripe\GraphQL\Dev\DevelopmentAdmin
- links:
- graphql: 'List GraphQL development tools'
-SilverStripe\GraphQL\Dev\DevelopmentAdmin:
- registered_controllers:
- build:
- controller: SilverStripe\GraphQL\Dev\Build
- links:
- build: Build the GraphQL schema
+ commands:
+ 'graphql/build': 'SilverStripe\GraphQL\Dev\Build'
diff --git a/_config/logging.yml b/_config/logging.yml
index e19a72a8..0de75516 100644
--- a/_config/logging.yml
+++ b/_config/logging.yml
@@ -4,7 +4,7 @@ after: '#logging'
---
SilverStripe\Core\Injector\Injector:
- # Omits the HTTPOutputHandler from the logger so errors won't appear in output
+ # Omits the ErrorOutputHandler from the logger so errors won't appear in output
Psr\Log\LoggerInterface.graphql-quiet:
type: singleton
class: Monolog\Logger
diff --git a/src/Dev/Build.php b/src/Dev/Build.php
index 97eb2b07..80b1b645 100644
--- a/src/Dev/Build.php
+++ b/src/Dev/Build.php
@@ -4,11 +4,10 @@
namespace SilverStripe\GraphQL\Dev;
use Psr\Log\LoggerInterface;
-use SilverStripe\Control\Controller;
-use SilverStripe\Control\Director;
-use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Injector\Injector;
-use SilverStripe\Dev\DebugView;
+use SilverStripe\Dev\DevelopmentAdmin;
+use SilverStripe\HybridExecution\Command\HybridCommand;
+use SilverStripe\HybridExecution\HybridOutput;
use SilverStripe\GraphQL\Schema\DataObject\FieldAccessor;
use SilverStripe\GraphQL\Schema\Exception\EmptySchemaException;
use SilverStripe\GraphQL\Schema\Exception\SchemaBuilderException;
@@ -18,38 +17,41 @@
use SilverStripe\GraphQL\Schema\SchemaBuilder;
use SilverStripe\GraphQL\Schema\Storage\CodeGenerationStore;
use SilverStripe\ORM\Connect\NullDatabaseException;
+use SilverStripe\Security\PermissionProvider;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
-class Build extends Controller
+class Build extends HybridCommand implements PermissionProvider
{
- private static $url_handlers = [
- '' => 'build'
- ];
+ protected static string $commandName = 'graphql:build';
+
+ protected static string $description = 'Build the GraphQL schema(s)';
- private static $allowed_actions = [
- 'build'
+ private static string|array|null $permissions_for_browser_execution = [
+ 'ADMIN',
+ 'ALL_DEV_ADMIN',
+ 'CAN_DEV_GRAPHQL',
];
- /**
- * @throws SchemaBuilderException
- * @throws SchemaNotFoundException
- */
- public function build(HTTPRequest $request): void
+ public function getTitle(): string
{
- $isBrowser = !Director::is_cli();
- if ($isBrowser) {
- $renderer = DebugView::create();
- echo $renderer->renderHeader();
- echo $renderer->renderInfo("GraphQL Schema Builder", Director::absoluteBaseURL());
- echo "
";
- }
- $clear = true;
-
- $this->buildSchema($request->getVar('schema'), $clear);
+ return 'GraphQL Schema Builder';
+ }
- if ($isBrowser) {
- echo "
";
- echo $renderer->renderFooter();
+ public function run(InputInterface $input, HybridOutput $output): int
+ {
+ $originalLogger = Injector::inst()->get(LoggerInterface::class . '.graphql-build');
+ try {
+ $logger = Logger::singleton();
+ $logger->setOutput($output);
+ Injector::inst()->registerService($logger, LoggerInterface::class . '.graphql-build');
+ $this->buildSchema($input->getOption('schema'), true, $output);
+ } finally {
+ // Restore default logger back to its starting state
+ Injector::inst()->registerService($originalLogger, LoggerInterface::class . '.graphql-build');
}
+ return Command::SUCCESS;
}
/**
@@ -97,15 +99,15 @@ public function buildSchema(string $key = null, bool $clear = true): void
break;
}
}
- $logger->warning("
- Your schema configuration requires access to the database. This can happen
+ $logger->warning(
+ "Your schema configuration requires access to the database. This can happen
when you add fields that require type introspection (i.e. custom getters).
It is recommended that you specify an explicit type when adding custom getters
- to your schema.");
+ to your schema."
+ );
if ($candidate) {
$logger->warning(sprintf(
- "
- This most likely happened when you tried to add the field '%s' to '%s'",
+ "This most likely happened when you tried to add the field '%s' to '%s'",
$candidate['args'][1],
get_class($candidate['args'][0])
));
@@ -114,9 +116,32 @@ public function buildSchema(string $key = null, bool $clear = true): void
throw $e;
}
- $logger->info(
- Benchmark::end('build-schema-' . $key, 'Built schema in %sms.')
- );
+ $logger->info(Benchmark::end('build-schema-' . $key, 'Built schema in %sms.'));
}
}
+
+ public function getOptions(): array
+ {
+ return [
+ new InputOption(
+ 'schema',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'The name of the schema to be built. If not passed, all schemas will be built',
+ suggestedValues: array_keys(Schema::config()->get('schemas') ?? [])
+ )
+ ];
+ }
+
+ public function providePermissions(): array
+ {
+ return [
+ 'CAN_DEV_GRAPHQL' => [
+ 'name' => _t(__CLASS__ . '.CAN_DEV_GRAPHQL_DESCRIPTION', 'Can view and execute /dev/graphql'),
+ 'help' => _t(__CLASS__ . '.CAN_DEV_GRAPHQL_HELP', 'Can view and execute GraphQL development tools (/dev/graphql).'),
+ 'category' => DevelopmentAdmin::permissionsCategory(),
+ 'sort' => 80
+ ],
+ ];
+ }
}
diff --git a/src/Dev/DevelopmentAdmin.php b/src/Dev/DevelopmentAdmin.php
deleted file mode 100644
index d1e29cc0..00000000
--- a/src/Dev/DevelopmentAdmin.php
+++ /dev/null
@@ -1,146 +0,0 @@
- 'index',
- '$Action' => 'runRegisteredController',
- ];
-
- private static $init_permissions = [
- 'ADMIN',
- 'ALL_DEV_ADMIN',
- 'CAN_DEV_GRAPHQL',
- ];
-
- protected function init()
- {
- parent::init();
-
- if (RootDevelopmentAdmin::config()->get('deny_non_cli') && !Director::is_cli()) {
- return $this->httpError(404);
- }
-
- if (!$this->canInit()) {
- Security::permissionFailure($this);
- }
-
- // Define custom logger
- $logger = Logger::singleton();
- Injector::inst()->registerService($logger, LoggerInterface::class . '.graphql-build');
- }
-
- public function index(HTTPRequest $request)
- {
- // Web mode
- if (!Director::is_cli()) {
- $renderer = DebugView::create();
- echo $renderer->renderHeader();
- echo $renderer->renderInfo("Silverstripe CMS GraphQL Tools", Director::absoluteBaseURL());
- $base = Director::baseURL();
-
- echo '';
- $evenOdd = "odd";
- foreach (DevelopmentAdmin::get_links() as $action => $description) {
- echo "- /dev/graphql/$action:"
- . " $description
\n";
- $evenOdd = ($evenOdd == "odd") ? "even" : "odd";
- }
-
- echo $renderer->renderFooter();
-
- // CLI mode
- } else {
- echo "SILVERSTRIPE CMS GRAPHQL TOOLS\n--------------------------\n\n";
- echo "You can execute any of the following commands:\n\n";
- foreach (DevelopmentAdmin::get_links() as $action => $description) {
- echo " sake dev/graphql/$action: $description\n";
- }
- echo "\n\n";
- }
- }
-
- public function runRegisteredController(HTTPRequest $request)
- {
- $controllerClass = null;
-
- $baseUrlPart = $request->param('Action');
- $reg = Config::inst()->get(static::class, 'registered_controllers');
- if (isset($reg[$baseUrlPart])) {
- $controllerClass = $reg[$baseUrlPart]['controller'];
- }
-
- if ($controllerClass && class_exists($controllerClass ?? '')) {
- return $controllerClass::create();
- }
-
- $msg = 'Error: no controller registered in ' . __CLASS__ . ' for: ' . $request->param('Action');
- if (Director::is_cli()) {
- throw new Exception($msg);
- } else {
- $this->httpError(404, $msg);
- }
- }
-
- public function canInit(): bool
- {
- return (
- Director::isDev()
- // We need to ensure that DevelopmentAdminTest can simulate permission failures when running
- // "dev/tasks" from CLI.
- || (Director::is_cli() && RootDevelopmentAdmin::config()->get('allow_all_cli'))
- || Permission::check(static::config()->get('init_permissions'))
- );
- }
-
- public function providePermissions(): array
- {
- return [
- 'CAN_DEV_GRAPHQL' => [
- 'name' => _t(__CLASS__ . '.CAN_DEV_GRAPHQL_DESCRIPTION', 'Can view and execute /dev/graphql'),
- 'help' => _t(__CLASS__ . '.CAN_DEV_GRAPHQL_HELP', 'Can view and execute GraphQL development tools (/dev/graphql).'),
- 'category' => RootDevelopmentAdmin::permissionsCategory(),
- 'sort' => 80
- ],
- ];
- }
-
- /**
- * @return array of url => description
- */
- protected static function get_links(): array
- {
- $links = [];
-
- $reg = Config::inst()->get(static::class, 'registered_controllers');
- foreach ($reg as $registeredController) {
- if (isset($registeredController['links'])) {
- foreach ($registeredController['links'] as $url => $desc) {
- $links[$url] = $desc;
- }
- }
- }
- return $links;
- }
-}
diff --git a/src/Extensions/DevBuildExtension.php b/src/Extensions/DbBuildExtension.php
similarity index 79%
rename from src/Extensions/DevBuildExtension.php
rename to src/Extensions/DbBuildExtension.php
index acc6ae07..e32b3c4a 100644
--- a/src/Extensions/DevBuildExtension.php
+++ b/src/Extensions/DbBuildExtension.php
@@ -5,16 +5,17 @@
use Psr\Log\LoggerInterface;
use SilverStripe\Core\Config\Configurable;
+use SilverStripe\Core\Extension;
use SilverStripe\Core\Injector\Injector;
+use SilverStripe\HybridExecution\Command\DbBuild;
+use SilverStripe\HybridExecution\HybridOutput;
use SilverStripe\GraphQL\Dev\Build;
use SilverStripe\GraphQL\Schema\Logger;
-use SilverStripe\ORM\DatabaseAdmin;
-use SilverStripe\Core\Extension;
/**
- * @extends Extension
+ * @extends Extension
*/
-class DevBuildExtension extends Extension
+class DbBuildExtension extends Extension
{
use Configurable;
@@ -23,7 +24,7 @@ class DevBuildExtension extends Extension
*/
private static bool $enabled = true;
- protected function onAfterBuild(): void
+ protected function onAfterBuild(HybridOutput $output): void
{
if (!static::config()->get('enabled')) {
return;
@@ -35,7 +36,7 @@ protected function onAfterBuild(): void
try {
// Define custom logger
$logger = Logger::singleton();
- $logger->setVerbosity(Logger::INFO);
+ $logger->setOutput($output);
Injector::inst()->registerService($logger, LoggerInterface::class . '.graphql-build');
Build::singleton()->buildSchema();
diff --git a/src/Schema/Logger.php b/src/Schema/Logger.php
index cfb49a6d..3285ad1f 100644
--- a/src/Schema/Logger.php
+++ b/src/Schema/Logger.php
@@ -1,11 +1,11 @@
output = $output;
+ }
+
public function setVerbosity(int $level): Logger
{
$this->level = $level;
@@ -118,18 +125,45 @@ public function warning(Stringable|string $message, array $context = []): void
public function output(string $msg, ?string $prefix = null, ?string $colour = null): void
{
- $cli = Director::is_cli();
- $formatted = sprintf(
- '%s%s%s%s',
- $colour && $cli ? $colour :'',
- $prefix ? '[' . $prefix . ']: ' : '',
- $colour && $cli ? Logger::RESET : '',
- $msg
- );
- if ($cli) {
- fwrite(STDOUT, $formatted . PHP_EOL);
- } else {
- echo $formatted . "
";
+ $prefix = $prefix ? '[' . $prefix . ']: ' : '';
+ $this->output?->writeln($this->colouriseText($prefix, $colour) . $msg);
+ if (!$this->output) {
+ $cli = Director::is_cli();
+ $formatted = sprintf(
+ '%s%s%s%s',
+ $colour && $cli ? $colour :'',
+ $prefix,
+ $colour && $cli ? Logger::RESET : '',
+ $msg
+ );
+ if ($cli) {
+ fwrite(STDOUT, $formatted . PHP_EOL);
+ } else {
+ echo $formatted . "
";
+ }
+ }
+ }
+
+ private function colouriseText(string $msg, ?string $colour): string
+ {
+ switch ($colour) {
+ case Logger::BLACK:
+ return "$msg>";
+ case Logger::RED:
+ return "$msg>";
+ case Logger::GREEN:
+ return "$msg>";
+ case Logger::YELLOW:
+ return "$msg>";
+ case Logger::BLUE:
+ return "$msg>";
+ case Logger::MAGENTA:
+ return "$msg>";
+ case Logger::CYAN:
+ return "$msg>";
+ case Logger::WHITE:
+ return "$msg>";
}
+ return $msg;
}
}