From 20269e5089eaf810aeea9d6a086d3f6273147467 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Fri, 1 Sep 2023 17:45:37 +0100 Subject: [PATCH 1/6] Added initial composer support for plugin management --- modules/system/classes/PluginBase.php | 10 +++ modules/system/classes/PluginManager.php | 66 ++++++++++++++ modules/system/classes/packager/Composer.php | 53 +++++++++++ .../packager/commands/RemoveCommand.php | 70 +++++++++++++++ .../packager/commands/RequireCommand.php | 77 ++++++++++++++++ .../classes/packager/commands/ShowCommand.php | 66 ++++++++++++++ .../packager/commands/UpdateCommand.php | 88 +++++++++++++++++++ modules/system/console/PluginList.php | 20 +++-- 8 files changed, 441 insertions(+), 9 deletions(-) create mode 100644 modules/system/classes/packager/Composer.php create mode 100644 modules/system/classes/packager/commands/RemoveCommand.php create mode 100644 modules/system/classes/packager/commands/RequireCommand.php create mode 100644 modules/system/classes/packager/commands/ShowCommand.php create mode 100644 modules/system/classes/packager/commands/UpdateCommand.php diff --git a/modules/system/classes/PluginBase.php b/modules/system/classes/PluginBase.php index 466279660b..63e6a490aa 100644 --- a/modules/system/classes/PluginBase.php +++ b/modules/system/classes/PluginBase.php @@ -53,6 +53,11 @@ class PluginBase extends ServiceProviderBase */ public $disabled = false; + /** + * @var ?string The composer package a plugin belongs to. + */ + public readonly ?string $package; + /** * Returns information about this plugin, including plugin name and developer name. * @@ -477,6 +482,11 @@ public function getPluginVersions(bool $includeScripts = true): array return $versions; } + public function setComposerPackage(?string $package): void + { + $this->package = $package; + } + /** * Verifies the plugin's dependencies are present and enabled */ diff --git a/modules/system/classes/PluginManager.php b/modules/system/classes/PluginManager.php index 5bc1d61fc1..94aaa7dc2c 100644 --- a/modules/system/classes/PluginManager.php +++ b/modules/system/classes/PluginManager.php @@ -14,6 +14,7 @@ use FilesystemIterator; use RecursiveIteratorIterator; use RecursiveDirectoryIterator; +use System\Classes\Packager\Composer; use System\Models\PluginVersion; use Winter\Storm\Foundation\Application; use Winter\Storm\Support\ClassLoader; @@ -61,6 +62,11 @@ class PluginManager */ protected $pluginFlags = []; + /** + * @var array Array of packages installed via composer + */ + protected $composerPackages = []; + /** * @var PluginVersion[] Local cache of loaded PluginVersion records keyed by plugin code */ @@ -108,6 +114,9 @@ protected function init(): void { $this->app = App::make('app'); + // Load the packages registered via composer + $this->loadComposer(); + // Load the plugins from the filesystem and sort them by dependencies $this->loadPlugins(); @@ -119,6 +128,53 @@ protected function init(): void $this->registerPluginReplacements(); } + public function loadComposer(): array + { + return $this->composerPackages = Cache::rememberForever(Composer::COMPOSER_CACHE_KEY, function () { + $outdated = Composer::show('outdated')['installed'] ?? []; + $outdated = array_combine(array_map(fn ($pack) => $pack['name'], $outdated), $outdated); + + $paths = Composer::show(path: true)['installed'] ?? []; + $paths = array_combine( + array_map(fn ($pack) => $pack['name'], $paths), + array_map(fn ($pack) => $pack['path'], $paths), + ); + + $packages = []; + foreach (Composer::show()['installed'] as $package) { + if ($package['direct-dependency']) { + if (isset($outdated[$package['name']])) { + $package['outdated'] = $outdated[$package['name']]; + } + if (isset($paths[$package['name']])) { + $package['path'] = $paths[$package['name']]; + if (is_file($paths[$package['name']] . '/composer.json')) { + $json = json_decode(file_get_contents($paths[$package['name']] . '/composer.json')); + if (isset($json->type)) { + $package['type'] = $json->type; + switch ($package['type']) { + case 'winter-plugin': + $packages['plugins'][$package['name']] = $package; + break; + case 'winter-module': + $packages['modules'][$package['name']] = $package; + break; + case 'winter-theme': + $packages['themes'][$package['name']] = $package; + break; + default: + break; + } + } + } + } + } + } + + return $packages; + }); + } + /** * Finds all available plugins and loads them in to the $this->plugins array. */ @@ -180,6 +236,16 @@ public function loadPlugin(string $namespace, string $path): ?PluginBase $this->plugins[$lowerClassId] = $pluginObj; $this->normalizedMap[$lowerClassId] = $classId; + $pluginObj->setComposerPackage(Cache::rememberForever($lowerClassId . '.composerPackage', function () use ($path) { + foreach ($this->composerPackages['plugins'] ?? [] as $name => $package) { + if (($package['path'] ?? '') === $path) { + return $name; + } + } + + return null; + })); + $replaces = $pluginObj->getReplaces(); if ($replaces) { foreach ($replaces as $replace) { diff --git a/modules/system/classes/packager/Composer.php b/modules/system/classes/packager/Composer.php new file mode 100644 index 0000000000..306579ea77 --- /dev/null +++ b/modules/system/classes/packager/Composer.php @@ -0,0 +1,53 @@ +|string + */ +class Composer +{ + public const COMPOSER_CACHE_KEY = 'winter.system.composer'; + + protected static PackagerComposer $composer; + + public static function make(bool $fresh = false): PackagerComposer + { + if (!$fresh && isset(static::$composer)) { + return static::$composer; + } + + static::$composer = new PackagerComposer(); + static::$composer->setWorkDir(base_path()); + + static::$composer->setCommand('show', new ShowCommand(static::$composer)); + static::$composer->setCommand('update', new UpdateCommand(static::$composer)); + static::$composer->setCommand('remove', new RemoveCommand(static::$composer)); + static::$composer->setCommand('require', new RequireCommand(static::$composer)); + + return static::$composer; + } + + public static function __callStatic(string $name, array $args = []): mixed + { + if (!isset(static::$composer)) { + static::make(); + } + + return static::$composer->{$name}(...$args); + } +} diff --git a/modules/system/classes/packager/commands/RemoveCommand.php b/modules/system/classes/packager/commands/RemoveCommand.php new file mode 100644 index 0000000000..e429d1e0d2 --- /dev/null +++ b/modules/system/classes/packager/commands/RemoveCommand.php @@ -0,0 +1,70 @@ +package = $package; + $this->dryRun = $dryRun; + } + + /** + * @inheritDoc + */ + public function arguments(): array + { + $arguments = []; + + if ($this->dryRun) { + $arguments['--dry-run'] = true; + } + + $arguments['packages'] = [$this->package]; + + return $arguments; + } + + public function execute() + { + $output = $this->runComposerCommand(); + $message = implode(PHP_EOL, $output['output']); + + if ($output['code'] !== 0) { + throw new CommandException($message); + } + + Cache::forget(ComposerFactory::COMPOSER_CACHE_KEY); + + return $message; + } + + /** + * @inheritDoc + */ + public function getCommandName(): string + { + return 'remove'; + } +} diff --git a/modules/system/classes/packager/commands/RequireCommand.php b/modules/system/classes/packager/commands/RequireCommand.php new file mode 100644 index 0000000000..44fcf25b31 --- /dev/null +++ b/modules/system/classes/packager/commands/RequireCommand.php @@ -0,0 +1,77 @@ +package = $package; + $this->dryRun = $dryRun; + $this->dev = $dev; + } + + /** + * @inheritDoc + */ + public function arguments(): array + { + $arguments = []; + + if ($this->dryRun) { + $arguments['--dry-run'] = true; + } + + if ($this->dev) { + $arguments['--dev'] = true; + } + + $arguments['packages'] = [$this->package]; + + return $arguments; + } + + public function execute() + { + $output = $this->runComposerCommand(); + $message = implode(PHP_EOL, $output['output']); + + if ($output['code'] !== 0) { + throw new CommandException($message); + } + + Cache::forget(ComposerFactory::COMPOSER_CACHE_KEY); + + return $message; + } + + /** + * @inheritDoc + */ + public function getCommandName(): string + { + return 'require'; + } +} diff --git a/modules/system/classes/packager/commands/ShowCommand.php b/modules/system/classes/packager/commands/ShowCommand.php new file mode 100644 index 0000000000..cb07fe2188 --- /dev/null +++ b/modules/system/classes/packager/commands/ShowCommand.php @@ -0,0 +1,66 @@ +path = $path; + } + + /** + * @inheritDoc + */ + public function arguments(): array + { + $arguments = []; + + if (!empty($this->package)) { + $arguments['package'] = $this->package; + } + + if ($this->mode !== 'installed') { + $arguments['--' . $this->mode] = true; + } + + if ($this->noDev) { + $arguments['--no-dev'] = true; + } + + if ($this->path) { + $arguments['--path'] = true; + } + + $arguments['--format'] = 'json'; + + return $arguments; + } +} diff --git a/modules/system/classes/packager/commands/UpdateCommand.php b/modules/system/classes/packager/commands/UpdateCommand.php new file mode 100644 index 0000000000..9f7bc03d20 --- /dev/null +++ b/modules/system/classes/packager/commands/UpdateCommand.php @@ -0,0 +1,88 @@ +executed) { + return; + } + + $this->includeDev = $includeDev; + $this->lockFileOnly = $lockFileOnly; + $this->ignorePlatformReqs = $ignorePlatformReqs; + $this->ignoreScripts = $ignoreScripts; + $this->dryRun = $dryRun; + $this->package = $package; + + if (in_array($installPreference, [self::PREFER_NONE, self::PREFER_DIST, self::PREFER_SOURCE])) { + $this->installPreference = $installPreference; + } + } + + /** + * @inheritDoc + */ + public function arguments(): array + { + $arguments = []; + + if ($this->dryRun) { + $arguments['--dry-run'] = true; + } + + if ($this->lockFileOnly) { + $arguments['--no-install'] = true; + } + + if ($this->ignorePlatformReqs) { + $arguments['--ignore-platform-reqs'] = true; + } + + if ($this->ignoreScripts) { + $arguments['--no-scripts'] = true; + } + + if (in_array($this->installPreference, [self::PREFER_DIST, self::PREFER_SOURCE])) { + $arguments['--prefer-' . $this->installPreference] = true; + } + + if ($this->package) { + $arguments['--'] = true; + $arguments[$this->package] = true; + } + + return $arguments; + } + + public function execute() + { + Cache::forget(ComposerFactory::COMPOSER_CACHE_KEY); + return parent::execute(); + } +} diff --git a/modules/system/console/PluginList.php b/modules/system/console/PluginList.php index f7883c34d4..b6d7f3d640 100644 --- a/modules/system/console/PluginList.php +++ b/modules/system/console/PluginList.php @@ -2,6 +2,7 @@ use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\TableSeparator; +use System\Classes\PluginManager; use System\Models\PluginVersion; use Winter\Storm\Console\Command; @@ -34,24 +35,25 @@ class PluginList extends Command */ public function handle() { - $allPlugins = PluginVersion::all(); - $pluginsCount = count($allPlugins); + $plugins = PluginManager::instance()->getAllPlugins(); + $pluginVersions = PluginVersion::all()->keyBy('code'); - if ($pluginsCount <= 0) { + if (count($plugins) <= 0) { $this->info('No plugin found'); return; } $rows = []; - foreach ($allPlugins as $plugin) { + foreach ($plugins as $plugin) { $rows[] = [ - $plugin->code, - $plugin->version, - (!$plugin->is_frozen) ? 'Yes': 'No', - (!$plugin->is_disabled) ? 'Yes': 'No', + $plugin->getPluginIdentifier(), + $plugin->package, + $plugin->getPluginVersion(), + (!$pluginVersions[$plugin->getPluginIdentifier()]->is_frozen) ? 'Yes': 'No', + (!$pluginVersions[$plugin->getPluginIdentifier()]->is_disabled) ? 'Yes': 'No', ]; } - $this->table(['Plugin name', 'Version', 'Updates enabled', 'Plugin enabled'], $rows); + $this->table(['Plugin name', 'Composer Package', 'Version', 'Updates enabled', 'Plugin enabled'], $rows); } } From c43bb1afcb83b5bc96456606962048b4c476fa32 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Tue, 5 Sep 2023 14:40:35 +0100 Subject: [PATCH 2/6] Added support for package detail loading from framework cache file --- modules/system/classes/PluginBase.php | 27 +++++++-- modules/system/classes/PluginManager.php | 76 ++++++++++-------------- 2 files changed, 54 insertions(+), 49 deletions(-) diff --git a/modules/system/classes/PluginBase.php b/modules/system/classes/PluginBase.php index 63e6a490aa..6e296733f8 100644 --- a/modules/system/classes/PluginBase.php +++ b/modules/system/classes/PluginBase.php @@ -54,9 +54,9 @@ class PluginBase extends ServiceProviderBase public $disabled = false; /** - * @var ?string The composer package a plugin belongs to. + * @var ?array The composer package details for this plugin. */ - public readonly ?string $package; + protected ?array $composerPackage = null; /** * Returns information about this plugin, including plugin name and developer name. @@ -482,9 +482,28 @@ public function getPluginVersions(bool $includeScripts = true): array return $versions; } - public function setComposerPackage(?string $package): void + /** + * Set the composer package property for the plugin + */ + public function setComposerPackage(?array $package): void + { + $this->composerPackage = $package; + } + + /** + * Get the composer package details + */ + public function getComposerPackage(): ?array + { + return $this->composerPackage; + } + + /** + * Get the composer package name + */ + public function getComposerPackageName(): ?string { - $this->package = $package; + return $this->composerPackage['name'] ?? null; } /** diff --git a/modules/system/classes/PluginManager.php b/modules/system/classes/PluginManager.php index 94aaa7dc2c..3117f7e37e 100644 --- a/modules/system/classes/PluginManager.php +++ b/modules/system/classes/PluginManager.php @@ -10,6 +10,7 @@ use Cache; use Config; use Schema; +use Storage; use SystemException; use FilesystemIterator; use RecursiveIteratorIterator; @@ -131,43 +132,25 @@ protected function init(): void public function loadComposer(): array { return $this->composerPackages = Cache::rememberForever(Composer::COMPOSER_CACHE_KEY, function () { - $outdated = Composer::show('outdated')['installed'] ?? []; - $outdated = array_combine(array_map(fn ($pack) => $pack['name'], $outdated), $outdated); - - $paths = Composer::show(path: true)['installed'] ?? []; - $paths = array_combine( - array_map(fn ($pack) => $pack['name'], $paths), - array_map(fn ($pack) => $pack['path'], $paths), - ); - + $packageFile = Storage::path('../framework/packages.json'); + if (!is_file($packageFile)) { + return []; + } $packages = []; - foreach (Composer::show()['installed'] as $package) { - if ($package['direct-dependency']) { - if (isset($outdated[$package['name']])) { - $package['outdated'] = $outdated[$package['name']]; - } - if (isset($paths[$package['name']])) { - $package['path'] = $paths[$package['name']]; - if (is_file($paths[$package['name']] . '/composer.json')) { - $json = json_decode(file_get_contents($paths[$package['name']] . '/composer.json')); - if (isset($json->type)) { - $package['type'] = $json->type; - switch ($package['type']) { - case 'winter-plugin': - $packages['plugins'][$package['name']] = $package; - break; - case 'winter-module': - $packages['modules'][$package['name']] = $package; - break; - case 'winter-theme': - $packages['themes'][$package['name']] = $package; - break; - default: - break; - } - } - } - } + $installed = json_decode(file_get_contents($packageFile), JSON_OBJECT_AS_ARRAY); + foreach ($installed as $package) { + switch ($package['type']) { + case 'winter-plugin': + $packages['plugins'][$package['path']] = $package; + break; + case 'winter-module': + $packages['modules'][$package['path']] = $package; + break; + case 'winter-theme': + $packages['themes'][$package['path']] = $package; + break; + default: + break; } } @@ -236,15 +219,7 @@ public function loadPlugin(string $namespace, string $path): ?PluginBase $this->plugins[$lowerClassId] = $pluginObj; $this->normalizedMap[$lowerClassId] = $classId; - $pluginObj->setComposerPackage(Cache::rememberForever($lowerClassId . '.composerPackage', function () use ($path) { - foreach ($this->composerPackages['plugins'] ?? [] as $name => $package) { - if (($package['path'] ?? '') === $path) { - return $name; - } - } - - return null; - })); + $pluginObj->setComposerPackage($this->composerPackages['plugins'][$path] ?? null); $replaces = $pluginObj->getReplaces(); if ($replaces) { @@ -537,6 +512,17 @@ public function findByIdentifier(PluginBase|string $identifier, bool $ignoreRepl return $this->plugins[$identifier] ?? null; } + public function findByComposerPackage(string $identifier): ?PluginBase + { + foreach ($this->getAllPlugins() as $plugin) { + if ($plugin->getComposerPackageName() === $identifier) { + return $plugin; + } + } + + return null; + } + /** * Checks to see if a plugin has been registered. */ From 5d5dca2e4bc02e64ef9cebd58cc6feced4529a3c Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Tue, 5 Sep 2023 14:41:27 +0100 Subject: [PATCH 3/6] Added packages --- composer.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 553c7ddb97..368d06e4ca 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,9 @@ "winter/wn-backend-module": "dev-develop", "winter/wn-cms-module": "dev-develop", "laravel/framework": "^9.1", - "wikimedia/composer-merge-plugin": "~2.1.0" + "winter/packager": "*", + "wikimedia/composer-merge-plugin": "~2.1.0", + "jaxwilko/composer-winter-plugin": "dev-main" }, "require-dev": { "phpunit/phpunit": "^9.5.8", @@ -82,7 +84,8 @@ "config": { "allow-plugins": { "composer/installers": true, - "wikimedia/composer-merge-plugin": true + "wikimedia/composer-merge-plugin": true, + "jaxwilko/composer-winter-plugin": true } } } From a65a6260d12a4eccbd786ef480d4fab17de7960f Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Tue, 5 Sep 2023 14:46:50 +0100 Subject: [PATCH 4/6] Added improvements to composer commands --- modules/system/classes/packager/Composer.php | 6 +++-- .../packager/commands/RemoveCommand.php | 4 ++-- .../packager/commands/RequireCommand.php | 4 ++-- .../packager/commands/SearchCommand.php | 23 +++++++++++++++++++ .../packager/commands/UpdateCommand.php | 4 ++-- 5 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 modules/system/classes/packager/commands/SearchCommand.php diff --git a/modules/system/classes/packager/Composer.php b/modules/system/classes/packager/Composer.php index 306579ea77..ad61c0a9d9 100644 --- a/modules/system/classes/packager/Composer.php +++ b/modules/system/classes/packager/Composer.php @@ -3,6 +3,7 @@ namespace System\Classes\Packager; use System\Classes\Packager\Commands\RemoveCommand; +use System\Classes\Packager\Commands\SearchCommand; use System\Classes\Packager\Commands\ShowCommand; use System\Classes\Packager\Commands\UpdateCommand; use System\Classes\Packager\Commands\RequireCommand; @@ -34,10 +35,11 @@ public static function make(bool $fresh = false): PackagerComposer static::$composer = new PackagerComposer(); static::$composer->setWorkDir(base_path()); - static::$composer->setCommand('show', new ShowCommand(static::$composer)); - static::$composer->setCommand('update', new UpdateCommand(static::$composer)); static::$composer->setCommand('remove', new RemoveCommand(static::$composer)); static::$composer->setCommand('require', new RequireCommand(static::$composer)); + static::$composer->setCommand('search', new SearchCommand(static::$composer)); + static::$composer->setCommand('show', new ShowCommand(static::$composer)); + static::$composer->setCommand('update', new UpdateCommand(static::$composer)); return static::$composer; } diff --git a/modules/system/classes/packager/commands/RemoveCommand.php b/modules/system/classes/packager/commands/RemoveCommand.php index e429d1e0d2..d533256438 100644 --- a/modules/system/classes/packager/commands/RemoveCommand.php +++ b/modules/system/classes/packager/commands/RemoveCommand.php @@ -3,7 +3,7 @@ namespace System\Classes\Packager\Commands; use Cache; -use System\Classes\Packager\ComposerFactory; +use System\Classes\Packager\Composer; use Winter\Packager\Commands\BaseCommand; use Winter\Packager\Exceptions\CommandException; @@ -55,7 +55,7 @@ public function execute() throw new CommandException($message); } - Cache::forget(ComposerFactory::COMPOSER_CACHE_KEY); + Cache::forget(Composer::COMPOSER_CACHE_KEY); return $message; } diff --git a/modules/system/classes/packager/commands/RequireCommand.php b/modules/system/classes/packager/commands/RequireCommand.php index 44fcf25b31..643c6c4334 100644 --- a/modules/system/classes/packager/commands/RequireCommand.php +++ b/modules/system/classes/packager/commands/RequireCommand.php @@ -3,7 +3,7 @@ namespace System\Classes\Packager\Commands; use Cache; -use System\Classes\Packager\ComposerFactory; +use System\Classes\Packager\Composer; use Winter\Packager\Commands\BaseCommand; use Winter\Packager\Exceptions\CommandException; @@ -62,7 +62,7 @@ public function execute() throw new CommandException($message); } - Cache::forget(ComposerFactory::COMPOSER_CACHE_KEY); + Cache::forget(Composer::COMPOSER_CACHE_KEY); return $message; } diff --git a/modules/system/classes/packager/commands/SearchCommand.php b/modules/system/classes/packager/commands/SearchCommand.php new file mode 100644 index 0000000000..0b45343fcd --- /dev/null +++ b/modules/system/classes/packager/commands/SearchCommand.php @@ -0,0 +1,23 @@ +runComposerCommand(); + + if ($output['code'] !== 0) { + throw new CommandException(implode(PHP_EOL, $output['output'])); + } + + $this->results = json_decode(implode(PHP_EOL, $output['output']), true) ?? []; + + return $this; + } +} diff --git a/modules/system/classes/packager/commands/UpdateCommand.php b/modules/system/classes/packager/commands/UpdateCommand.php index 9f7bc03d20..8a15ec9680 100644 --- a/modules/system/classes/packager/commands/UpdateCommand.php +++ b/modules/system/classes/packager/commands/UpdateCommand.php @@ -3,7 +3,7 @@ namespace System\Classes\Packager\Commands; use Cache; -use System\Classes\Packager\ComposerFactory; +use System\Classes\Packager\Composer; use Winter\Packager\Commands\Update; class UpdateCommand extends Update @@ -82,7 +82,7 @@ public function arguments(): array public function execute() { - Cache::forget(ComposerFactory::COMPOSER_CACHE_KEY); + Cache::forget(Composer::COMPOSER_CACHE_KEY); return parent::execute(); } } From d337f0c20666fa60f93c3438cccda04e28e2b06d Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Tue, 5 Sep 2023 14:47:34 +0100 Subject: [PATCH 5/6] Added package cache to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3582897e81..667d3c33cb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ composer.lock .env.php selenium.php /bootstrap/compiled.php +/storage/framework/packages.json .phpunit.result.cache # Hosting ignores From 6559506fe3cd0da1c7039d0213db50ef88b4ff57 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Tue, 5 Sep 2023 14:50:18 +0100 Subject: [PATCH 6/6] Added support for installing and removing composer packages to plugin commands --- modules/system/console/PluginInstall.php | 70 ++++++++++++++++++++---- modules/system/console/PluginRemove.php | 12 ++++ 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/modules/system/console/PluginInstall.php b/modules/system/console/PluginInstall.php index 113fe40268..791a029283 100644 --- a/modules/system/console/PluginInstall.php +++ b/modules/system/console/PluginInstall.php @@ -1,8 +1,11 @@ argument('plugin'); - $manager = UpdateManager::instance()->setNotesOutput($this->output); + $manager = null; - $pluginDetails = $manager->requestPluginDetails($pluginName); - - $code = array_get($pluginDetails, 'code'); - $hash = array_get($pluginDetails, 'hash'); + if (strpos($pluginName, '/')) { + $code = $this->composerInstall($pluginName); + if (!$code) { + return; + } + } elseif (strpos($pluginName, '.')) { + $manager = UpdateManager::instance()->setNotesOutput($this->output); + $code = $this->winterInstall($pluginName, $manager); + } - $this->output->writeln(sprintf('Downloading plugin: %s', $code)); - $manager->downloadPlugin($code, $hash, true); - - $this->output->writeln(sprintf('Unpacking plugin: %s', $code)); - $manager->extractPlugin($code, $hash); + if (!$manager) { + $manager = UpdateManager::instance()->setNotesOutput($this->output); + } /* * Make sure plugin is registered @@ -64,4 +70,46 @@ public function handle() $this->output->writeln(sprintf('Migrating plugin...', $code)); $manager->updatePlugin($code); } + + public function winterInstall(string $pluginName, UpdateManager $manager): string + { + $pluginDetails = $manager->requestPluginDetails($pluginName); + + $code = array_get($pluginDetails, 'code'); + $hash = array_get($pluginDetails, 'hash'); + + $this->output->writeln(sprintf('Downloading plugin: %s', $code)); + $manager->downloadPlugin($code, $hash, true); + + $this->output->writeln(sprintf('Unpacking plugin: %s', $code)); + $manager->extractPlugin($code, $hash); + + return $code; + } + + public function composerInstall(string $pluginName): ?string + { + try { + $result = Composer::search($pluginName, 'winter-plugin')->getResults(); + + if (count($result) > 1) { + throw new SystemException('More than 1 plugin returned via composer search'); + } + + if (count($result) === 0) { + throw new SystemException('Plugin could not be found'); + } + + Composer::require($pluginName); + } catch (\Throwable $e) { + $this->error($e->getMessage()); + return null; + } + + PluginManager::forgetInstance(); + UpdateManager::forgetInstance(); + VersionManager::forgetInstance(); + + return PluginManager::instance()->findByComposerPackage($pluginName)->getPluginIdentifier(); + } } diff --git a/modules/system/console/PluginRemove.php b/modules/system/console/PluginRemove.php index d442fb1514..55cb5d03b7 100644 --- a/modules/system/console/PluginRemove.php +++ b/modules/system/console/PluginRemove.php @@ -1,6 +1,7 @@ rollbackPlugin($pluginName); } + $plugin = $pluginManager->findByIdentifier($pluginName); + + /** + * Uninstall composer package + */ + if ($package = $plugin->getComposerPackageName()) { + $this->output->writeln(sprintf('Removing composer package: %s', $package)); + $this->output->write(Composer::remove($package) . PHP_EOL); + return 0; + } + /* * Delete from file system */