Open chillu opened 3 years ago
From https://getcomposer.org/doc/articles/scripts.md
Note: Only scripts defined in the root package's composer.json are executed. If a dependency of the root package specifies its own scripts, Composer does not execute those additional scripts.
https://getcomposer.org/doc/articles/plugins.md
Plugin packages are automatically loaded as soon as they are installed and will be loaded when Composer starts up if they are found in the current project's list of installed packages. You may pass the --no-plugins option to Composer commands to disable all installed plugins. This may be particularly helpful if any of the plugins causes errors and you wish to update or uninstall it.
We don't require our current plugins (silverstripe/vendor-plugin
and silverstripe/recipe-plugin
) in projects root composer.json, which is proof that we can solve GraphQL the same way - as long as composer install
is executed without --no-plugins
Update on this: There's a composer plugin now. The problem is that executing through sake and even just booting the kernel requires a database connection, which means we can't run this a build environment (e.g. CI) that's separate from the runtime environment - see WIP pull request.
I think the 'Inclusion Option 1' makes the most sense, it seems to be the closest to the module that actually works with it. Installer or recipe-plugin are too far from the problem from my perspective.
You've already mentioned the edge case where the generation will need to be compatible with codebases where the generated code was already committed to the repo. I'd also check how it behaves on fresh installs with pre-existing project code defining custom types.
Had a long chat with Aaron, and we've tried to isolate the need for a database-less execution from GraphQL code gen a bit, which required choosing between a few different options for core behaviour.
At its core, Silverstripe is a web application framework, and since the ORM is bundled into the "core" the assumption of a valid database connection is pretty strong. The database connection is established lazy (when DB::get_conn()
is first called, usually as part of executing a query. But the configuration for this connection is validated on Kernel->boot()
.
There are uncommon but still valid scenarios where a database connection is not available:
There is a WIP pull request for adding a NullDatabase to framework, a fake that throws exceptions if the database is used. In the first scenario (GraphQL code gen) it can point at docs to describe why querying is a bad idea in this context. It also relies on a DatabaselessKernel
to avoid boot failures due to lack of valid database config. These classes are marked as @internal
, and are used in a GraphQL composer plugin, rather than in cli-script.php
. Ideally we could enable cli-script.php
(sake
) to opt out of database usage as well.
This would be the most pluggable (for custom cli-script.php
and index.php
).
But it also exposes core internals: We couldn't add a new Bootstrapper to core afterwards.
It would also extend the public API surface, and break custom Kernel implementations
if we enforced the new API via the Kernel
interface.
class CoreKernel
{
protected $bootstrappers = [
SilverStripe\Core\Bootstrapper\PHP::class,
SilverStripe\Core\Bootstrapper\Manifest::class,
SilverStripe\Core\Bootstrapper\DatabaseConfig::class,
// ...
];
public function setBootstrappers(array $bs)
{
$this->bootstrappers = $bs;
}
}
Big API surface increase, but retains our flexibility to alter boot logic in core afterwards (unlike Option 1).
class CoreKernel
{
protected $bootDatabase = true;
public function boot()
{
$this->bootPHP();
if ($this->bootDatabase) {
$this->bootDatabase()
}
}
}
Special purpose kernel, which worked when we just used it in
the GraphQL composer plugin. It's messier now that we need it in cli-script.php
.
class DatabaselessKernel extends CoreKernel
{
public function boot()
{
$this->bootPHP();
// leave out $this->bootDatabase()
// ...
}
}
Move validateDatabase
out of CoreKernel into DB, and perform config validation on
DB::get_conn()
instead. This method avoids explicitly signaling the intent
to boot without a database (e.g. sake dev/graphql/build --no-database
),
and doesn't require changes on cli-script.php
or index.php
.
This will force us to also remove the non-functional redirectToInstaller()
call, which is acceptable because the installer-wizard
is broken and unsupported anyway.
With any of the options proposed the framework can boot without requiring a database,
or failing on a missing database config (until the first query).
We also need to call DB::set_conn(new NullDatabase())
in SilverStripe\GraphQL\Dev\DevelopmentAdmin
.
This ensures that we can throw specific error messages when the database is used
as part of the GraphQL code gen, regardless of how this was triggered (composer plugin vs. sake).
Databaseless Option 4, since it avoids API changes as well as changes to highlevel usage (such as sake --no-database
). I've added a few more tasks to the issue description to work through the implementation.
This makes it consistent with the
vendor-expose
approach, and ensures that both new and upgrading devs have a good experience (especially when they don't read upgrading guides).As a reminder, there's the following ways of generating GraphQL code. Only the
sake dev/graphql/build
on dev envs and committing the result to the codebase. Multi server safe.dev/build
(on dev or production runtime envs). Not multi server safe.sake dev/graphql/build
on production runtime envs. Not multi server safe./graphql
handlers (withSilverStripe\GraphQL\Controller::$autobuildSchema
defaulting totrue
). Not multi server safe.We propose adding a new default method:
composer install
andcomposer update
. That only works for GraphQL configuration added/updated through modules, you'd still needsake dev/graphql/build
to develop your own APIs. But the advantage here is that these commands already run as part of deployment processes, and likely would transparently include the new.graphql/
folder.Note: Our own platforms run
composer install --no-scripts && composer vendor-expose
for security reasons, so we'll still need to add a new command to those.Inclusion Options
There are various options on how to introduce this new logic
Inclusion Option 1: Require
silverstripe/graphql-composer-plugin
insilverstripe/graphql
. This means everyone will run GraphQL code generation oncomposer install
andcomposer update
by default when upgrading to CMS 4.9, unless they opt out viaSS_GRAPHQL_COMPOSER_BUILD_SCHEMAS=''
. The opt-out would only be required if the generated GraphQL code is checked in to the codebase (ganky), or if there are alternative ways to generate it during deployment or at runtime in production.Inclusion Option 2: Require
silverstripe/graphql-composer-plugin
insilverstripe/installer
. This means only new installs will get it by default, and we'd strongly recommend opt-in to any sites upgrading to CMS 4.9. A bit more "room for error" in this new implementation, but it'll lead to lots of confusion when the "runtime fallbacks" described above kick in and behave inconsistently between servers.Inclusion Option 3: Add this logic to the
silverstripe/recipe-plugin
instead of a new composer plugin, which is already required by framework. In this case, we need to make the code generation a no-op in case graphql isn't installed.Tasks
sake dev/graphql/build
SilverStripe\GraphQL\Dev\DevelopmentAdmin
(add more specific friendly error to instance)Pull Requests
Testing this feature
Shortcut to set up a project for this (mostly for my own reference):
Now you can simulate a
composer install
run:There's more validation about operating this change on Silverstripe's own platforms in an internal ticket