From ad9d3c5a8fc757a13ac1d6c057cf81ffe10c0aa0 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:59:30 +1200 Subject: [PATCH] API Update API to reflect changes to CLI interaction (#58) --- README.md | 12 ++- _config/config.yml | 33 ++------ composer.json | 10 +-- src/Clear.php | 70 ---------------- src/GraphQLSchemaInitTask.php | 154 +++++++++++----------------------- src/SchemaClear.php | 58 +++++++++++++ 6 files changed, 125 insertions(+), 212 deletions(-) delete mode 100644 src/Clear.php create mode 100644 src/SchemaClear.php diff --git a/README.md b/README.md index 5890942..076e738 100644 --- a/README.md +++ b/README.md @@ -16,17 +16,15 @@ This module adds an implementation of [graphiql](https://github.com/graphql/grap ### Accessing the IDE -**In GraphQL 3.x**, it can be accessed at `/dev/graphiql/`. - -**In GraphQL 4.x+**, it can be accessed at `/dev/graphql/ide`. +It can be accessed at `/dev/graphql/ide`. This is because GraphQL 4+ has its own `DevelopmentAdmin` controller. -The GraphQL v4 version of the module allows you to clear your schema by calling the `/dev/graphql/clear` task. +The GraphQL v4 version of the module allows you to clear your schema by running `sake graphql:clear`. ## Security -By default, the tool has the same restrictions as other development tools like `dev/build`: +By default, the tool has the same restrictions as other development tools like `/dev/build`: * In "dev" mode, it's available without authentication * In "test" and "live" mode, it requires ADMIN permissions @@ -100,13 +98,13 @@ You must be in CLI mode to use this task To view help for the task to see what options are available: ```bash -vendor/bin/sake dev/tasks/GraphQLSchemaInitTask help=1 +vendor/bin/sake tasks:GraphQLSchemaInitTask --help ``` To run the task with with minimal options: ```bash -vendor/bin/sake dev/tasks/GraphQLSchemaInitTask namespace=App +vendor/bin/sake tasks:GraphQLSchemaInitTask --namespace=App ``` diff --git a/_config/config.yml b/_config/config.yml index 0a99422..39478ad 100644 --- a/_config/config.yml +++ b/_config/config.yml @@ -9,33 +9,14 @@ SilverStripe\GraphQLDevTools\Controller: default_schema: default --- -Name: graphql4-devtool-routes +Name: devtool-routes After: - 'graphql-dev' -Only: - classexists: 'SilverStripe\GraphQL\Schema\Schema' --- SilverStripe\GraphQL\Dev\DevelopmentAdmin: - registered_controllers: - ide: - controller: 'SilverStripe\GraphQLDevTools\Controller' - links: - ide: Run the GraphQL IDE - clear: - controller: SilverStripe\GraphQLDevTools\Clear - links: - clear: Clear the GraphQL schema - ---- -Name: graphql3-devtool-routes -After: - - 'graphql-dev' -Except: - classexists: 'SilverStripe\GraphQL\Schema\Schema' ---- -SilverStripe\Dev\DevelopmentAdmin: - registered_controllers: - graphiql: - controller: 'SilverStripe\GraphQLDevTools\Controller' -SilverStripe\GraphQLDevTools\Controller: - default_route: 'graphql' + commands: + 'graphql/clear': 'SilverStripe\GraphQLDevTools\SchemaClear' + controllers: + 'graphql/ide': + class: 'SilverStripe\GraphQLDevTools\Controller' + description: Run the GraphQL IDE diff --git a/composer.json b/composer.json index 9acaa51..90ce595 100644 --- a/composer.json +++ b/composer.json @@ -1,16 +1,16 @@ { "name": "silverstripe/graphql-devtools", - "description": "Tools to help developers building new applications on SilverStripe’s GraphQL API", + "description": "Tools to help developers building new applications on SilverStripe's GraphQL API", "type": "silverstripe-vendormodule", "license": "BSD-3-Clause", "require": { - "silverstripe/graphql": "^3 || ^4 || ^5 || ^6", - "symfony/finder": "^4 || ^5 || ^6 || ^7", - "symfony/filesystem": "^4 || ^5 || ^6 || ^7" + "silverstripe/graphql": "^6", + "symfony/finder": "^7", + "symfony/filesystem": "^7" }, "require-dev": { "phpunit/phpunit": "^11.3", - "silverstripe/framework": "^4.10", + "silverstripe/framework": "^6", "squizlabs/php_codesniffer": "^3" }, "autoload": { diff --git a/src/Clear.php b/src/Clear.php deleted file mode 100644 index 2004431..0000000 --- a/src/Clear.php +++ /dev/null @@ -1,70 +0,0 @@ - 'clear', - ]; - - private static $allowed_actions = [ - 'clear', - ]; - - public function __construct() - { - parent::__construct(); - if (!method_exists(Deprecation::class, 'withSuppressedNotice') - || !method_exists(Deprecation::class, 'notice') - ) { - return; - } - Deprecation::withSuppressedNotice(function () { - Deprecation::notice( - '1.1.0', - 'Will be replaced with SilverStripe\GraphQLDevTools\SchemaClear', - Deprecation::SCOPE_CLASS - ); - }); - } - - public function clear(HTTPRequest $request): void - { - $logger = Injector::inst()->get(LoggerInterface::class . '.graphql-build'); - $dirName = CodeGenerationStore::config()->get('dirName'); - $expectedPath = BASE_PATH . DIRECTORY_SEPARATOR . $dirName; - $fs = new Filesystem(); - - $finder = new Finder(); - // Make finder not recursive - $finder->depth('== 0'); - - $logger->info('Clearing GraphQL code generation directory'); - if ($fs->exists($expectedPath)) { - $logger->info('Directory has been found'); - if ($finder->in($expectedPath)->hasResults()) { - foreach ($finder as $file) { - $logger->info('Removing ' . $file->getFilename()); - $fs->remove($file->getRealPath()); - } - $logger->info('Directory now empty'); - } else { - $logger->info('Directory is already empty.'); - } - } else { - $logger->info('Directory was not found. There is nothing to clear'); - } - } -} diff --git a/src/GraphQLSchemaInitTask.php b/src/GraphQLSchemaInitTask.php index c074586..57dee78 100644 --- a/src/GraphQLSchemaInitTask.php +++ b/src/GraphQLSchemaInitTask.php @@ -2,25 +2,26 @@ namespace SilverStripe\GraphQLDevTools; -use SilverStripe\Control\Director; -use SilverStripe\Control\HTTPRequest; +use Composer\Console\Input\InputOption; use SilverStripe\Core\Manifest\ModuleManifest; use SilverStripe\Core\Path; use SilverStripe\Dev\BuildTask; -use SilverStripe\GraphQL\Config\Configuration; +use SilverStripe\PolyExecution\PolyOutput; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; /** * A task that initialises a GraphQL 4+ schema with boilerplate config and files. */ class GraphQLSchemaInitTask extends BuildTask { - private static $segment = 'GraphQLSchemaInitTask'; + private static bool $can_run_in_cli = false; - protected $enabled = true; + protected static string $commandName = 'GraphQLSchemaInitTask'; - protected $title = 'Initialise a new GraphQL schema'; + protected static string $description = 'Boilerplate setup for a new GraphQL schema'; - protected $description = 'Boilerplate setup for a new GraphQL schema'; + protected string $title = 'Initialise a new GraphQL schema'; private string $appNamespace; @@ -38,79 +39,38 @@ class GraphQLSchemaInitTask extends BuildTask private string $perms = ''; - /** - * @param HTTPRequest $request - */ - public function run($request) + protected function execute(InputInterface $input, PolyOutput $output): int { - if (!Director::is_cli()) { - echo "This task can only be run from CLI\n"; - return; - } - - if (!class_exists(Configuration::class)) { - echo "This task requires GraphQL v4+ to be installed\n"; - return; - } - - if ($request->getVar('help')) { - $this->showHelp(); - return; - } - - $appNamespace = $request->getVar('namespace'); - - if (!$appNamespace) { - $segment = static::$segment; - echo "Please provide a base namespace for your app, e.g. \"namespace=App\" or \"namespace=MyVendor\MyProject\".\nFor help, run \"dev/tasks/$segment help=1\"\n"; - return; - } - - $this->appNamespace = $appNamespace; - $this->projectDir = ModuleManifest::config()->get('project'); + $this->appNamespace = $input->getOption('namespace'); + $this->schemaName = $input->getOption('name'); + $this->graphqlConfigDir = $input->getOption('graphqlConfigDir'); + $this->graphqlCodeDir = $input->getOption('graphqlCodeDir'); + $this->endpoint = $input->getOption('endpoint'); + $this->srcDir = $input->getOption('srcDir'); - $schemaName = $request->getVar('name'); - if ($schemaName) { - $this->schemaName = $schemaName; - } - - $graphqlConfigDir = $request->getVar('graphqlConfigDir'); - if ($graphqlConfigDir) { - $this->graphqlConfigDir = $graphqlConfigDir; - } - - $graphqlCodeDir = $request->getVar('graphqlCodeDir'); - if ($graphqlCodeDir) { - $this->graphqlCodeDir = $graphqlCodeDir; - } - - $endpoint = $request->getVar('endpoint'); - if ($endpoint) { - $this->endpoint = $endpoint; - } - - $srcDir = $request->getVar('srcDir'); - if ($srcDir) { - $this->srcDir = $srcDir; + if (!$this->appNamespace) { + $output->writeln('Please provide a base namespace for your app, e.g. --namespace=App or --namespace=MyVendor\MyProject'); + return Command::INVALID; } $absProjectDir = Path::join(BASE_PATH, $this->projectDir); $this->perms = fileperms($absProjectDir); - $this->createGraphQLConfig(); - $this->createProjectConfig(); - $this->createResolvers(); + $this->createGraphQLConfig($output); + $this->createProjectConfig($output); + $this->createResolvers($output); + return Command::SUCCESS; } /** * Creates the SS config in _config/graphql.yml */ - private function createProjectConfig(): void + private function createProjectConfig(PolyOutput $output): void { $absConfigFile = Path::join(BASE_PATH, $this->projectDir, '_config', 'graphql.yml'); if (file_exists($absConfigFile)) { - echo "Config file $absConfigFile already exists. Skipping." . PHP_EOL; + $output->writeln("Config file $absConfigFile already exists. Skipping."); return; } @@ -138,7 +98,7 @@ class: SilverStripe\GraphQL\Controller SilverStripe\Control\Director: rules: $rules - + SilverStripe\GraphQL\Schema\Schema: schemas: $this->schemaName: @@ -154,15 +114,15 @@ class: SilverStripe\GraphQL\Controller /** * Creates the graphql schema specific config in _graphql/ */ - private function createGraphQLConfig(): void + private function createGraphQLConfig(PolyOutput $output): void { $absGraphQLDir = Path::join(BASE_PATH, $this->projectDir, $this->graphqlConfigDir); if (is_dir($absGraphQLDir)) { - echo "GraphQL config directory already exists. Skipping." . PHP_EOL; + $output->writeln("GraphQL config directory already exists. Skipping."); return; } - echo "Creating graphql config directory: $this->graphqlConfigDir" . PHP_EOL; + $output->writeln("Creating graphql config directory: $this->graphqlConfigDir"); mkdir($absGraphQLDir, $this->perms); foreach (['models', 'config', 'types', 'queries', 'mutations'] as $file) { touch(Path::join($absGraphQLDir, "$file.yml")); @@ -190,7 +150,7 @@ private function createGraphQLConfig(): void /** * Creates an example resolvers class for autodiscovery in app/src/GraphQL/Resolvers.php */ - private function createResolvers(): void + private function createResolvers(PolyOutput $output): void { $absSrcDir = Path::join(BASE_PATH, $this->projectDir, $this->srcDir); $absGraphQLCodeDir = Path::join($absSrcDir, $this->graphqlCodeDir); @@ -199,11 +159,11 @@ private function createResolvers(): void str_replace('/', '\\', $this->graphqlCodeDir) ]); if (is_dir($absGraphQLCodeDir)) { - echo "GraphQL code dir $this->graphqlCodeDir already exists. Skipping" . PHP_EOL; + $output->writeln("GraphQL code dir $this->graphqlCodeDir already exists. Skipping"); return; } - echo "Creating resolvers class in $graphqlNamespace" . PHP_EOL; + $output->writeln("Creating resolvers class in $graphqlNamespace"); mkdir($absGraphQLCodeDir, $this->perms, true); $resolverFile = Path::join($absGraphQLCodeDir, 'Resolvers.php'); $moreInfo = 'https://docs.silverstripe.org/en/developer_guides/graphql/' @@ -235,44 +195,30 @@ public static function resolveMyQuery(\$obj, array \$args, array \$context): arr file_put_contents($resolverFile, $resolverCode); } - /** - * Outputs help text to the console - */ - private function showHelp(): void + public function getOptions(): array { - $segment = static::$segment; - echo <<get('dirName'); + $expectedPath = BASE_PATH . DIRECTORY_SEPARATOR . $dirName; + $fs = new Filesystem(); + + $finder = new Finder(); + // Make finder not recursive + $finder->depth('== 0'); + + if ($fs->exists($expectedPath)) { + $output->writeln('Directory has been found'); + if ($finder->in($expectedPath)->hasResults()) { + foreach ($finder as $file) { + $output->writeln('Removing ' . $file->getFilename()); + $fs->remove($file->getRealPath()); + } + $output->writeln('Directory now empty'); + } else { + $output->writeln('Directory is already empty.'); + } + } else { + $output->writeln('Directory was not found. There is nothing to clear'); + } + return Command::SUCCESS; + } + + protected function getHeading(): string + { + return 'Clearing GraphQL code generation directory'; + } +}