From c85774a16af51b02af754c9fec743a3b693c1b5b Mon Sep 17 00:00:00 2001 From: Death-Satan <49744633+Death-Satan@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:46:12 +0800 Subject: [PATCH 01/14] Added Handling of plugin identity annotations, plugin base classes, plugin boot, and register methods. --- src/app-store/src/Abstracts/AbstractExtension.php | 8 ++++++++ src/app-store/src/Annotation/MineExtension.php | 12 ++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/app-store/src/Abstracts/AbstractExtension.php create mode 100644 src/app-store/src/Annotation/MineExtension.php diff --git a/src/app-store/src/Abstracts/AbstractExtension.php b/src/app-store/src/Abstracts/AbstractExtension.php new file mode 100644 index 00000000..2f40c1d4 --- /dev/null +++ b/src/app-store/src/Abstracts/AbstractExtension.php @@ -0,0 +1,8 @@ + Date: Tue, 6 Feb 2024 12:02:14 +0800 Subject: [PATCH 02/14] code style --- src/app-store/publish/trans/en.php | 4 +- .../src/Abstracts/AbstractExtension.php | 65 ++++++++++++++++++- .../src/Annotation/MineExtension.php | 20 ++++-- .../src/Listener/AppStoreListener.php | 21 +++++- .../src/Service/Impl/AppStoreServiceImpl.php | 12 +++- src/mine-core/src/Traits/MapperTrait.php | 2 - tests/Pest.php | 41 +++--------- tests/TestCase.php | 2 +- 8 files changed, 120 insertions(+), 47 deletions(-) diff --git a/src/app-store/publish/trans/en.php b/src/app-store/publish/trans/en.php index c9bdb8d8..5c257a49 100644 --- a/src/app-store/publish/trans/en.php +++ b/src/app-store/publish/trans/en.php @@ -1,4 +1,6 @@ -client->post($uri, [ 'json' => $data, @@ -47,7 +52,10 @@ public function request(string $uri, array $data = []) throw new \RuntimeException(trans('app-store.store.response_fail')); } $result = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); - return $response; + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \RuntimeException(json_last_error_msg()); + } + return $result; } private function getAccessToken(): string diff --git a/src/mine-core/src/Traits/MapperTrait.php b/src/mine-core/src/Traits/MapperTrait.php index ea680023..99b3d0ec 100644 --- a/src/mine-core/src/Traits/MapperTrait.php +++ b/src/mine-core/src/Traits/MapperTrait.php @@ -12,11 +12,9 @@ namespace Mine\Traits; -use Hyperf\Context\ApplicationContext; use Hyperf\Contract\LengthAwarePaginatorInterface; use Hyperf\Database\Model\Builder; use Hyperf\Database\Model\Model; -use Hyperf\ModelCache\Manager; use Mine\Annotation\Transaction; use Mine\Exception\MineException; use Mine\Exception\NormalStatusException; diff --git a/tests/Pest.php b/tests/Pest.php index f626f890..25161724 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -9,35 +9,14 @@ * @contact root@imoi.cn * @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE */ -// uses(Tests\TestCase::class)->in('Feature'); +uses(Mine\Tests\TestCase::class) + ->beforeEach(function (){ + $mockConfig = Mockery::mock(\Hyperf\Contract\ConfigInterface::class); + $mockConfig->allows('has')->andReturn(true); + $mockConfig->allows('get')->andReturn([]); + $mockConfig->allows('set')->andReturn(true); + \Hyperf\Context\ApplicationContext::getContainer() + ->set(\Hyperf\Contract\ConfigInterface::class,$mockConfig); + }) + ->in('Feature'); -/* -|-------------------------------------------------------------------------- -| Expectations -|-------------------------------------------------------------------------- -| -| When you're writing tests, you often need to check that values meet certain conditions. The -| "expect()" function gives you access to a set of "expectations" methods that you can use -| to assert different things. Of course, you may extend the Expectation API at any time. -| -*/ - -expect()->extend('toBeOne', function () { - return $this->toBe(1); -}); - -/* -|-------------------------------------------------------------------------- -| Functions -|-------------------------------------------------------------------------- -| -| While Pest is very powerful out-of-the-box, you may have some testing code specific to your -| project that you don't want to repeat in every file. Here you can also expose helpers as -| global functions to help you to reduce the number of lines of code in your test files. -| -*/ - -function something() -{ - // .. -} diff --git a/tests/TestCase.php b/tests/TestCase.php index 18f2b040..4d1334e4 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -10,7 +10,7 @@ * @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE */ -namespace Tests; +namespace Mine\Tests; use PHPUnit\Framework\TestCase as BaseTestCase; From d943435e48fbd822d949685cca1eec0d592c72e9 Mon Sep 17 00:00:00 2001 From: Death-Satan <49744633+Death-Satan@users.noreply.github.com> Date: Tue, 6 Feb 2024 14:50:02 +0800 Subject: [PATCH 03/14] code style --- .../src/Abstracts/AbstractExtension.php | 1 - tests/Pest.php | 23 ++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/app-store/src/Abstracts/AbstractExtension.php b/src/app-store/src/Abstracts/AbstractExtension.php index 35fa7a4c..a08f6b4f 100644 --- a/src/app-store/src/Abstracts/AbstractExtension.php +++ b/src/app-store/src/Abstracts/AbstractExtension.php @@ -34,7 +34,6 @@ abstract class AbstractExtension */ public string $homePage; - public function __construct( public ContainerInterface $container ) {} diff --git a/tests/Pest.php b/tests/Pest.php index 25161724..820075a8 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -9,14 +9,25 @@ * @contact root@imoi.cn * @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE */ -uses(Mine\Tests\TestCase::class) - ->beforeEach(function (){ - $mockConfig = Mockery::mock(\Hyperf\Contract\ConfigInterface::class); +use Hyperf\Context\ApplicationContext; +use Hyperf\Contract\ConfigInterface; +use Mine\Tests\TestCase; + +/** + * This file is part of MineAdmin. + * + * @see https://www.mineadmin.com + * @document https://doc.mineadmin.com + * @contact root@imoi.cn + * @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE + */ +uses(TestCase::class) + ->beforeEach(function () { + $mockConfig = Mockery::mock(ConfigInterface::class); $mockConfig->allows('has')->andReturn(true); $mockConfig->allows('get')->andReturn([]); $mockConfig->allows('set')->andReturn(true); - \Hyperf\Context\ApplicationContext::getContainer() - ->set(\Hyperf\Contract\ConfigInterface::class,$mockConfig); + ApplicationContext::getContainer() + ->set(ConfigInterface::class, $mockConfig); }) ->in('Feature'); - From 1611759724a8ae4101bb002e31536b87cdafd5b4 Mon Sep 17 00:00:00 2001 From: Death-Satan <49744633+Death-Satan@users.noreply.github.com> Date: Tue, 6 Feb 2024 15:11:27 +0800 Subject: [PATCH 04/14] code style --- tests/Pest.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/Pest.php b/tests/Pest.php index 820075a8..40de252f 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -13,14 +13,6 @@ use Hyperf\Contract\ConfigInterface; use Mine\Tests\TestCase; -/** - * This file is part of MineAdmin. - * - * @see https://www.mineadmin.com - * @document https://doc.mineadmin.com - * @contact root@imoi.cn - * @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE - */ uses(TestCase::class) ->beforeEach(function () { $mockConfig = Mockery::mock(ConfigInterface::class); From ae90687b0acd4da9fd4f53a5c8ba2aa375151f91 Mon Sep 17 00:00:00 2001 From: Death-Satan <49744633+Death-Satan@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:06:09 +0800 Subject: [PATCH 05/14] Initialize mine.json configuration file specification, add sapi interface for querying local extension lists --- src/app-store/composer.json | 3 +- src/app-store/publish/mine-extension.php | 62 ++++++++++ .../src/Command/ExtensionInstallCommand.php | 55 +++++++++ .../src/Command/ExtensionLocalListCommand.php | 50 ++++++++ src/app-store/src/ConfigProvider.php | 6 + src/app-store/src/Service/AppStoreService.php | 17 ++- .../src/Service/Impl/AppStoreServiceImpl.php | 114 +++++++++++++++++- .../tests/Feature/AppstoreServiceTest.php | 72 ++++++++++- 8 files changed, 370 insertions(+), 9 deletions(-) create mode 100644 src/app-store/publish/mine-extension.php create mode 100644 src/app-store/src/Command/ExtensionInstallCommand.php create mode 100644 src/app-store/src/Command/ExtensionLocalListCommand.php diff --git a/src/app-store/composer.json b/src/app-store/composer.json index 0e12b7e5..6105d869 100644 --- a/src/app-store/composer.json +++ b/src/app-store/composer.json @@ -4,7 +4,8 @@ "type": "library", "require": { "xmo/mine-core": "v2.0.x-dev", - "xmo/mine-service": "v2.0.x-dev" + "xmo/mine-service": "v2.0.x-dev", + "hyperf/translation": "~3.1.0" }, "require-dev": { "pestphp/pest": "^2.33" diff --git a/src/app-store/publish/mine-extension.php b/src/app-store/publish/mine-extension.php new file mode 100644 index 00000000..be4cbd89 --- /dev/null +++ b/src/app-store/publish/mine-extension.php @@ -0,0 +1,62 @@ + env('APP_DEBUG'), + /* + * MineAdmin + */ + 'access_token' => env('MINE_ACCESS_TOKEN'), + /* + * The root directory where the front-end code resides. + * + * 前端代码所在根目录. + */ + 'front_directory' => BASE_PATH . '/web', + 'composer' => [ + /* + * composer executes the program directly from composer by default, + * but if there is an environment restriction, + * you can specify something like /usr/bin/composer. + * + * + * composer 执行程序 默认直接是 composer 如果有环境限制 可以指定比如 /usr/bin/composer + */ + 'bin' => 'composer', + ], + 'npm' => [ + /* + * Front-end package management execution tools Optional npm yarn pnpm, default npm is used + * + * + * 前端包管理执行工具 可选 npm yarn pnpm,默认使用 npm + */ + 'type' => 'npm', + /* + * The default directory for executing programs is npm, + * but if you don't have the npm environment variable configured, + * you can manually specify the program to execute, e.g. /usr/local/npm/bin/npm. + * + * + * 执行程序所在目录 默认是直接执行 npm, 如果没有配置 npm 环境变量 则可以手动指定 执行程序 例如 /usr/local/npm/bin/npm + */ + 'bin' => 'npm', + ], +]; diff --git a/src/app-store/src/Command/ExtensionInstallCommand.php b/src/app-store/src/Command/ExtensionInstallCommand.php new file mode 100644 index 00000000..74474745 --- /dev/null +++ b/src/app-store/src/Command/ExtensionInstallCommand.php @@ -0,0 +1,55 @@ +output->info('Start initialization'); + $this->output->info('Publishing multilingual documents'); + $publishPath = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'publish'; + $languages = BASE_PATH . '/storage/languages'; + FileSystem::copy($publishPath . '/trans/en.php', $languages . '/en/app-store.php'); + FileSystem::copy($publishPath . '/trans/zh-CN.php', $languages . '/zh_CN/app-store.php'); + $this->output->success('Language file published successfully'); + $this->output->info('Publishing Configuration Files'); + FileSystem::copy($publishPath . '/mine-extension.php', BASE_PATH . '/config/autoload/mine-extension.php'); + $this->output->success('Publishing Configuration File Succeeded'); + $this->output->warning(' + 接下来选择开发模式, + 默认是用 ./web 目录作为前端源码所在目录, + 也可以配置 mine-extension.php 配置文件 + 手动指定前端源代码开发目录'); + $this->output->warning(' + Next, select the development mode. + The default is to use . /web directory as the front-end source code directory. + You can also configure the mine-extension.php configuration file + Manually specify the front-end source code development directory'); + } + + protected function configure() + { + $this->addOption('force', 'f', InputOption::VALUE_NONE, 'Whether or not coverage is mandatory'); + } +} diff --git a/src/app-store/src/Command/ExtensionLocalListCommand.php b/src/app-store/src/Command/ExtensionLocalListCommand.php new file mode 100644 index 00000000..38721cbd --- /dev/null +++ b/src/app-store/src/Command/ExtensionLocalListCommand.php @@ -0,0 +1,50 @@ +get(AppStoreService::class); + $list = $service->getLocalExtensions(); + $headers = [ + 'extensionName', 'description', 'author', 'homePage', 'status', + ]; + $rows = []; + foreach ($list as $info) { + $current = []; + $current[] = $info['name']; + $current[] = $info['description']; + if (is_string($info['author'])) { + $current[] = $info['author']; + } else { + $current[] = $info['author'][0]['name'] ?? '--'; + } + $current[] = $info['homePage'] ?? '--'; + $current[] = $info['status'] ? 'installed' : 'uninstalled'; + $rows[] = $current; + } + $this->table($headers, $rows); + } +} diff --git a/src/app-store/src/ConfigProvider.php b/src/app-store/src/ConfigProvider.php index 068da107..c89e3a37 100644 --- a/src/app-store/src/ConfigProvider.php +++ b/src/app-store/src/ConfigProvider.php @@ -12,6 +12,9 @@ namespace Xmo\AppStore; +use Xmo\AppStore\Service\AppStoreService; +use Xmo\AppStore\Service\Impl\AppStoreServiceImpl; + class ConfigProvider { public function __invoke() @@ -25,6 +28,9 @@ public function __invoke() ], ], ], + 'dependencies' => [ + AppStoreService::class => AppStoreServiceImpl::class, + ], ]; } } diff --git a/src/app-store/src/Service/AppStoreService.php b/src/app-store/src/Service/AppStoreService.php index 7f7fb4ff..98931f3b 100644 --- a/src/app-store/src/Service/AppStoreService.php +++ b/src/app-store/src/Service/AppStoreService.php @@ -12,4 +12,19 @@ namespace Xmo\AppStore\Service; -interface AppStoreService {} +use GuzzleHttp\Exception\GuzzleException; + +interface AppStoreService +{ + /** + * Get all locally installed extensions. + * @throws \JsonException + */ + public function getLocalExtensions(): array; + + /** + * @throws GuzzleException + * @throws \JsonException + */ + public function request(string $uri, array $data = []): array; +} diff --git a/src/app-store/src/Service/Impl/AppStoreServiceImpl.php b/src/app-store/src/Service/Impl/AppStoreServiceImpl.php index 456ebe8c..b4b7c7ff 100644 --- a/src/app-store/src/Service/Impl/AppStoreServiceImpl.php +++ b/src/app-store/src/Service/Impl/AppStoreServiceImpl.php @@ -14,7 +14,9 @@ use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException; +use Hyperf\Contract\ConfigInterface; use Hyperf\Guzzle\ClientFactory; +use Symfony\Component\Finder\Finder; use Xmo\AppStore\Service\AppStoreService; use function Hyperf\Support\env; @@ -22,18 +24,19 @@ final class AppStoreServiceImpl implements AppStoreService { - private readonly string $appStoreServer; - private readonly Client $client; + private readonly array $config; + public function __construct( - ClientFactory $clientFactory + ClientFactory $clientFactory, + ConfigInterface $config ) { - $this->appStoreServer = 'https://www.mineadmin.com/server/store/'; $this->client = $clientFactory->create([ - 'base_uri' => $this->appStoreServer, + 'base_uri' => 'https://www.mineadmin.com/server/store/', 'timeout' => 10.0, ]); + $this->config = $config->get('mine-extension'); } /** @@ -58,12 +61,111 @@ public function request(string $uri, array $data = []): array return $result; } + /** + * Get all locally extensions. + * @throws \JsonException + */ + public function getLocalExtensions(): array + { + $extensionPath = $this->getLocalExtensionsPath(); + if (! is_dir($extensionPath)) { + return []; + } + $finder = Finder::create() + ->files() + ->name('mine.json') + ->in($extensionPath); + if (! $finder->hasResults()) { + return []; + } + $result = []; + foreach ($finder as $file) { + $path = $file->getPath(); + /* + * @var \SplFileInfo $file + */ + $info = $this->readExtensionInfo($path); + $info['status'] = file_exists($path . '/install.lock'); + $result[] = $info; + } + return $result; + } + + /** + * Check if the given parameter is a valid configuration item. + */ + public function checkPlugin(array $info): bool + { + $checkOption = [ + 'name', 'description', 'author', 'require', 'description', 'dependencies', + 'installScript', 'uninstallScript', + ]; + foreach ($checkOption as $option) { + // Checking mine.json configuration + if (! array_key_exists($option, $info)) { + $this->throwCheckExtension($info['name'] ?? '--'); + } + $installScript = $info['installScript']; + $uninstallScript = $info['uninstallScript']; + if (! class_exists($installScript) || ! class_exists($uninstallScript)) { + throw new \RuntimeException( + sprintf('Installation and uninstallation scripts configured with the extension %s do not take effect and are not valid classes.', $info['name'] ?? '--') + ); + } + } + return true; + } + + /** + * Get local plugin directory. + */ + public function getLocalExtensionsPath(): string + { + return $this->getBasePath() . DIRECTORY_SEPARATOR . $this->getExtensionDirectory(); + } + + /** + * Get MineAdmin developer credentials. + */ private function getAccessToken(): string { - $accessToken = env('MINE_ACCESS_TOKEN'); + $accessToken = $this->config['access_token'] ?? env('MINE_ACCESS_TOKEN'); if (empty($accessToken)) { throw new \RuntimeException(trans('app-store.access_token_null')); } return (string) $accessToken; } + + /** + * Read the specified directory to get the extension details. + * @throws \JsonException + */ + private function readExtensionInfo(string $path): array + { + $extensionJson = $path . DIRECTORY_SEPARATOR . 'mine.json'; + if (! file_exists($extensionJson)) { + throw new \RuntimeException(sprintf('The catalog %s is not a valid mine extension,because it\'s missing the necessary mine.json file.', $path)); + } + $info = json_decode(file_get_contents($extensionJson), true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \RuntimeException(sprintf('Error reading mine.json file in %s,' . json_last_error_msg(), $path)); + } + $this->checkPlugin($info); + return $info; + } + + private function throwCheckExtension(string $path): void + { + throw new \RuntimeException(sprintf('The catalog %s is not a valid mine extension package because the mine.json file it belongs to is missing key configurations such as `name` `author` `dependencies` `description` `require` `installScript` `uninstallScript` and so on.', $path)); + } + + private function getBasePath(): string + { + return BASE_PATH; + } + + private function getExtensionDirectory(): string + { + return 'plugin'; + } } diff --git a/src/app-store/tests/Feature/AppstoreServiceTest.php b/src/app-store/tests/Feature/AppstoreServiceTest.php index adc3995a..084556be 100644 --- a/src/app-store/tests/Feature/AppstoreServiceTest.php +++ b/src/app-store/tests/Feature/AppstoreServiceTest.php @@ -9,12 +9,82 @@ * @contact root@imoi.cn * @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE */ +use GuzzleHttp\Client; use Hyperf\Context\ApplicationContext; +use Hyperf\Contract\ConfigInterface; +use Hyperf\Contract\TranslatorInterface; +use Hyperf\Guzzle\ClientFactory; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; use Xmo\AppStore\Service\Impl\AppStoreServiceImpl; beforeEach(function () { + putenv('MINE_ACCESS_TOKEN=xxxx1'); + ApplicationContext::getContainer()->set(TranslatorInterface::class, new class() implements TranslatorInterface { + public function trans(string $key, array $replace = [], ?string $locale = null): array|string + { + return 'xxx'; + } + + public function transChoice(string $key, $number, array $replace = [], ?string $locale = null): string + { + return ''; + } + + public function getLocale(): string + { + return 'zh-CN'; + } + + public function setLocale(string $locale) + { + // TODO: Implement setLocale() method. + } + }); + $mockClientFactory = Mockery::mock(ClientFactory::class); + $mockClient = Mockery::mock(Client::class); + $response = Mockery::mock(ResponseInterface::class); + $response->allows('getStatusCode')->andReturn(200, 500); + $content = Mockery::mock(StreamInterface::class); + $remoteResult = [ + 'data' => [], + 'message' => 'success', + ]; + $result = json_encode($remoteResult, JSON_UNESCAPED_UNICODE); + $content->allows('getContents') + ->andReturn($result, $result, ''); + $response->allows('getBody')->andReturn($content); + $mockClient->allows('post') + ->andReturn($response); + $mockClientFactory->allows('create')->andReturn($mockClient); + ApplicationContext::getContainer()->set(ClientFactory::class, $mockClientFactory); + ApplicationContext::getContainer()->set(ConfigInterface::class, new class() implements ConfigInterface { + public function get(string $key, mixed $default = null): mixed + { + return null; + } + + public function has(string $keys): bool + { + return true; + } + + public function set(string $key, mixed $value): void {} + }); $this->mock = ApplicationContext::getContainer()->get(AppStoreServiceImpl::class); }); -it('app store', function () { +test('request test', function () { expect(true)->toBeTrue(); + $response = $this->mock->request('/aaaa', ['xxx' => 'xxx']); + expect($response)->toBeArray(); + try { + $response = $this->mock->request('/aaaa', ['xxx' => 'xxx']); + } catch (RuntimeException $e) { + expect($e)->toBeInstanceOf(RuntimeException::class); + } + try { + $response = $this->mock->request('/aaaa', ['xxx' => 'xxx']); + } catch (RuntimeException $e) { + expect($e)->toBeInstanceOf(RuntimeException::class); + } }); From 26afc3d22385f80783e2364067f7d6de5deb8883 Mon Sep 17 00:00:00 2001 From: Death-Satan <49744633+Death-Satan@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:36:38 +0800 Subject: [PATCH 06/14] install local extension --- .../src/Service/ExtensionManagerService.php | 18 +++++ .../src/Service/Impl/AppStoreServiceImpl.php | 69 +++++++++++++++---- 2 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 src/app-store/src/Service/ExtensionManagerService.php diff --git a/src/app-store/src/Service/ExtensionManagerService.php b/src/app-store/src/Service/ExtensionManagerService.php new file mode 100644 index 00000000..15886090 --- /dev/null +++ b/src/app-store/src/Service/ExtensionManagerService.php @@ -0,0 +1,18 @@ +client = $clientFactory->create([ 'base_uri' => 'https://www.mineadmin.com/server/store/', @@ -124,23 +131,11 @@ public function getLocalExtensionsPath(): string return $this->getBasePath() . DIRECTORY_SEPARATOR . $this->getExtensionDirectory(); } - /** - * Get MineAdmin developer credentials. - */ - private function getAccessToken(): string - { - $accessToken = $this->config['access_token'] ?? env('MINE_ACCESS_TOKEN'); - if (empty($accessToken)) { - throw new \RuntimeException(trans('app-store.access_token_null')); - } - return (string) $accessToken; - } - /** * Read the specified directory to get the extension details. * @throws \JsonException */ - private function readExtensionInfo(string $path): array + public function readExtensionInfo(string $path): array { $extensionJson = $path . DIRECTORY_SEPARATOR . 'mine.json'; if (! file_exists($extensionJson)) { @@ -154,6 +149,52 @@ private function readExtensionInfo(string $path): array return $info; } + /** + * Installation of local plug-ins. + * @return string + * @throws \JsonException + */ + public function installExtension(string $path): void + { + Db::transaction(function () use ($path) { + $info = $this->readExtensionInfo($path); + if ($info['status']) { + throw new \RuntimeException(sprintf('The given directory %s is the directory where the plugin has already been installed.', $path)); + } + $installScript = $info['installScript']; + $installScriptInstance = ApplicationContext::getContainer()->make($installScript); + // 执行数据库迁移 seeder + $this->migrator->run([ + $path, + ]); + $this->seed->run([ + $path, + ]); + if (method_exists($installScriptInstance, 'handle')) { + $installScriptInstance->handle(); + return; + } + if (method_exists($installScriptInstance, '__invoke')) { + $installScriptInstance(); + return; + } + + // todo 前端文件迁移 + }); + } + + /** + * Get MineAdmin developer credentials. + */ + private function getAccessToken(): string + { + $accessToken = $this->config['access_token'] ?? env('MINE_ACCESS_TOKEN'); + if (empty($accessToken)) { + throw new \RuntimeException(trans('app-store.access_token_null')); + } + return (string) $accessToken; + } + private function throwCheckExtension(string $path): void { throw new \RuntimeException(sprintf('The catalog %s is not a valid mine extension package because the mine.json file it belongs to is missing key configurations such as `name` `author` `dependencies` `description` `require` `installScript` `uninstallScript` and so on.', $path)); From b3a4ba60da571df3beae878009232da3490f6c5e Mon Sep 17 00:00:00 2001 From: Death-Satan <49744633+Death-Satan@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:04:49 +0800 Subject: [PATCH 07/14] Improve plug-in installation mechanism --- .../src/Service/Impl/AppStoreServiceImpl.php | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/app-store/src/Service/Impl/AppStoreServiceImpl.php b/src/app-store/src/Service/Impl/AppStoreServiceImpl.php index e747f940..6239f77b 100644 --- a/src/app-store/src/Service/Impl/AppStoreServiceImpl.php +++ b/src/app-store/src/Service/Impl/AppStoreServiceImpl.php @@ -21,6 +21,7 @@ use Hyperf\Database\Seeders\Seeder; use Hyperf\DbConnection\Db; use Hyperf\Guzzle\ClientFactory; +use Nette\Utils\FileSystem; use Symfony\Component\Finder\Finder; use Xmo\AppStore\Service\AppStoreService; @@ -163,23 +164,26 @@ public function installExtension(string $path): void } $installScript = $info['installScript']; $installScriptInstance = ApplicationContext::getContainer()->make($installScript); - // 执行数据库迁移 seeder - $this->migrator->run([ - $path, - ]); - $this->seed->run([ - $path, - ]); - if (method_exists($installScriptInstance, 'handle')) { - $installScriptInstance->handle(); - return; - } + /** + * The seeder and databases of the directory where the plugin is located are the data migration directories. + * The . /web directory directly into the specified front-end source code directory. + * and execute the plugin's installation script afterwards + */ + $this->migrator->run([$path]); + $this->seed->run([$path]); if (method_exists($installScriptInstance, '__invoke')) { $installScriptInstance(); - return; } - - // todo 前端文件迁移 + /** + * If the plugin has a front-end file, copy it. + */ + if (file_exists($path.'/web')){ + $front_directory = $this->config['front_directory']; + if (!file_exists($front_directory)){ + throw new \RuntimeException('The front-end source code directory does not exist or does not have permission to write to it'); + } + FileSystem::copy($path.'/web',$front_directory); + } }); } From 9c22bac8649787652c3a6a0ce0d1444ab73682e5 Mon Sep 17 00:00:00 2001 From: Death-Satan <49744633+Death-Satan@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:53:29 +0800 Subject: [PATCH 08/14] Plug-in installation and uninstallation commands --- .../src/Command/ExtensionInitialCommand.php | 55 +++++++++++ .../src/Command/ExtensionInstallCommand.php | 60 +++++++----- .../src/Command/ExtensionUninstallCommand.php | 67 ++++++++++++++ src/app-store/src/Service/AppStoreService.php | 16 ++++ .../src/Service/Impl/AppStoreServiceImpl.php | 91 +++++++++++++------ src/app-store/src/Utils/FileSystemUtils.php | 48 ++++++++++ 6 files changed, 285 insertions(+), 52 deletions(-) create mode 100644 src/app-store/src/Command/ExtensionInitialCommand.php create mode 100644 src/app-store/src/Command/ExtensionUninstallCommand.php create mode 100644 src/app-store/src/Utils/FileSystemUtils.php diff --git a/src/app-store/src/Command/ExtensionInitialCommand.php b/src/app-store/src/Command/ExtensionInitialCommand.php new file mode 100644 index 00000000..0be70cd2 --- /dev/null +++ b/src/app-store/src/Command/ExtensionInitialCommand.php @@ -0,0 +1,55 @@ +output->info('Start initialization'); + $this->output->info('Publishing multilingual documents'); + $publishPath = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'publish'; + $languages = BASE_PATH . '/storage/languages'; + FileSystem::copy($publishPath . '/trans/en.php', $languages . '/en/app-store.php'); + FileSystem::copy($publishPath . '/trans/zh-CN.php', $languages . '/zh_CN/app-store.php'); + $this->output->success('Language file published successfully'); + $this->output->info('Publishing Configuration Files'); + FileSystem::copy($publishPath . '/mine-extension.php', BASE_PATH . '/config/autoload/mine-extension.php'); + $this->output->success('Publishing Configuration File Succeeded'); + $this->output->warning(' + 接下来选择开发模式, + 默认是用 ./web 目录作为前端源码所在目录, + 也可以配置 mine-extension.php 配置文件 + 手动指定前端源代码开发目录'); + $this->output->warning(' + Next, select the development mode. + The default is to use . /web directory as the front-end source code directory. + You can also configure the mine-extension.php configuration file + Manually specify the front-end source code development directory'); + } + + protected function configure() + { + $this->addOption('force', 'f', InputOption::VALUE_NONE, 'Whether or not coverage is mandatory'); + } +} diff --git a/src/app-store/src/Command/ExtensionInstallCommand.php b/src/app-store/src/Command/ExtensionInstallCommand.php index 74474745..6e6117d5 100644 --- a/src/app-store/src/Command/ExtensionInstallCommand.php +++ b/src/app-store/src/Command/ExtensionInstallCommand.php @@ -14,42 +14,54 @@ use Hyperf\Command\Annotation\Command; use Hyperf\Command\Command as Base; -use Nette\Utils\FileSystem; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; +use Xmo\AppStore\Service\AppStoreService; #[Command] class ExtensionInstallCommand extends Base { protected ?string $name = 'mine-extension:install'; - protected string $description = 'MineAdmin Extended Store Initialization Command Line'; + protected string $description = 'Installing Plugin Commands'; - public function __invoke(): void + public function __construct( + private readonly AppStoreService $appStoreService + ) { + parent::__construct(); + } + + public function __invoke() { - $this->output->info('Start initialization'); - $this->output->info('Publishing multilingual documents'); - $publishPath = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'publish'; - $languages = BASE_PATH . '/storage/languages'; - FileSystem::copy($publishPath . '/trans/en.php', $languages . '/en/app-store.php'); - FileSystem::copy($publishPath . '/trans/zh-CN.php', $languages . '/zh_CN/app-store.php'); - $this->output->success('Language file published successfully'); - $this->output->info('Publishing Configuration Files'); - FileSystem::copy($publishPath . '/mine-extension.php', BASE_PATH . '/config/autoload/mine-extension.php'); - $this->output->success('Publishing Configuration File Succeeded'); - $this->output->warning(' - 接下来选择开发模式, - 默认是用 ./web 目录作为前端源码所在目录, - 也可以配置 mine-extension.php 配置文件 - 手动指定前端源代码开发目录'); - $this->output->warning(' - Next, select the development mode. - The default is to use . /web directory as the front-end source code directory. - You can also configure the mine-extension.php configuration file - Manually specify the front-end source code development directory'); + $path = $this->input->getArgument('path'); + $yes = $this->input->getOption('yes'); + $pluginPath = BASE_PATH . '/plugin/' . $path; + if (! file_exists($pluginPath)) { + $this->output->error(sprintf('Plugin directory %s does not exist', $pluginPath)); + return; + } + $info = $this->appStoreService->readExtensionInfo($pluginPath); + + $headers = ['Extension name', 'author', 'description', 'homepage']; + $rows[] = [ + $info['name'], + is_string($info['author']) ? $info['author'] : ($info['author'][0]['name'] ?? '--'), + $info['description'], + $info['homepage'] ?? '--', + ]; + $this->table($headers, $rows); + $confirm = $yes ?: $this->confirm('Enter to start the installation', true); + if (! $confirm) { + $this->output->success('Installation has been successfully canceled'); + return; + } + $this->appStoreService->installExtension($pluginPath); + $this->output->success(sprintf('Plugin %s installed successfully', $pluginPath)); } protected function configure() { - $this->addOption('force', 'f', InputOption::VALUE_NONE, 'Whether or not coverage is mandatory'); + $this->addArgument('path', InputArgument::REQUIRED, 'Plug-in Catalog (relative path)'); + $this->addOption('yes', 'y', InputOption::VALUE_NONE, 'silent installation'); } } diff --git a/src/app-store/src/Command/ExtensionUninstallCommand.php b/src/app-store/src/Command/ExtensionUninstallCommand.php new file mode 100644 index 00000000..38bccc6b --- /dev/null +++ b/src/app-store/src/Command/ExtensionUninstallCommand.php @@ -0,0 +1,67 @@ +input->getArgument('path'); + $yes = $this->input->getOption('yes'); + $pluginPath = BASE_PATH . '/plugin/' . $path; + if (! file_exists($pluginPath)) { + $this->output->error(sprintf('Plugin directory %s does not exist', $pluginPath)); + return; + } + $info = $this->appStoreService->readExtensionInfo($pluginPath); + + $headers = ['Extension name', 'author', 'description', 'homepage']; + $rows[] = [ + $info['name'], + is_string($info['author']) ? $info['author'] : ($info['author'][0]['name'] ?? '--'), + $info['description'], + $info['homepage'] ?? '--', + ]; + $this->table($headers, $rows); + $confirm = $yes ?: $this->confirm('Enter to start the installation', true); + if (! $confirm) { + $this->output->success('Installation has been successfully canceled'); + return; + } + $this->appStoreService->uninstallExtension($pluginPath); + $this->output->success(sprintf('Plugin %s uninstalled successfully', $pluginPath)); + } + + protected function configure() + { + $this->addArgument('path', InputArgument::REQUIRED, 'Plug-in Catalog (relative path)'); + $this->addOption('yes', 'y', InputOption::VALUE_NONE, 'silent installation'); + } +} diff --git a/src/app-store/src/Service/AppStoreService.php b/src/app-store/src/Service/AppStoreService.php index 98931f3b..8b0d7a67 100644 --- a/src/app-store/src/Service/AppStoreService.php +++ b/src/app-store/src/Service/AppStoreService.php @@ -27,4 +27,20 @@ public function getLocalExtensions(): array; * @throws \JsonException */ public function request(string $uri, array $data = []): array; + + /** + * Read the specified directory to get the extension details. + * @throws \JsonException + */ + public function readExtensionInfo(string $path): array; + + /** + * Installation of local plug-ins. + */ + public function installExtension(string $path): void; + + /** + * Uninstall locally installed plug-ins. + */ + public function uninstallExtension(string $path): void; } diff --git a/src/app-store/src/Service/Impl/AppStoreServiceImpl.php b/src/app-store/src/Service/Impl/AppStoreServiceImpl.php index 6239f77b..c980b08e 100644 --- a/src/app-store/src/Service/Impl/AppStoreServiceImpl.php +++ b/src/app-store/src/Service/Impl/AppStoreServiceImpl.php @@ -19,11 +19,11 @@ use Hyperf\Database\Migrations\Migrator; use Hyperf\Database\Seeders\Seed; use Hyperf\Database\Seeders\Seeder; -use Hyperf\DbConnection\Db; use Hyperf\Guzzle\ClientFactory; use Nette\Utils\FileSystem; use Symfony\Component\Finder\Finder; use Xmo\AppStore\Service\AppStoreService; +use Xmo\AppStore\Utils\FileSystemUtils; use function Hyperf\Support\env; use function Hyperf\Translation\trans; @@ -152,39 +152,74 @@ public function readExtensionInfo(string $path): array /** * Installation of local plug-ins. - * @return string - * @throws \JsonException */ public function installExtension(string $path): void { - Db::transaction(function () use ($path) { - $info = $this->readExtensionInfo($path); - if ($info['status']) { - throw new \RuntimeException(sprintf('The given directory %s is the directory where the plugin has already been installed.', $path)); + $info = $this->readExtensionInfo($path); + if (file_exists($path . '/install.lock')) { + throw new \RuntimeException(sprintf('The given directory %s is the directory where the plugin has already been installed.', $path)); + } + $installScript = $info['installScript']; + $installScriptInstance = ApplicationContext::getContainer()->make($installScript); + + /* + * The seeder and databases of the directory where the plugin is located are the data migration directories. + * The . /web directory directly into the specified front-end source code directory. + * and execute the plugin's installation script afterwards + */ + $this->migrator->run([$path . '/Database/Migrations']); + $this->seed->run([$path . '/Database/Seeders']); + if (method_exists($installScriptInstance, '__invoke')) { + $installScriptInstance(); + } + /* + * If the plugin has a front-end file, copy it. + */ + if (file_exists($path . '/web')) { + $front_directory = $this->config['front_directory']; + if (! file_exists($front_directory)) { + throw new \RuntimeException('The front-end source code directory does not exist or does not have permission to write to it'); } - $installScript = $info['installScript']; - $installScriptInstance = ApplicationContext::getContainer()->make($installScript); - /** - * The seeder and databases of the directory where the plugin is located are the data migration directories. - * The . /web directory directly into the specified front-end source code directory. - * and execute the plugin's installation script afterwards - */ - $this->migrator->run([$path]); - $this->seed->run([$path]); - if (method_exists($installScriptInstance, '__invoke')) { - $installScriptInstance(); + // todo 整个web 目录移植。目前这个方法不行。因为卸载的时候恢复不了源文件 + FileSystemUtils::copyDirectory($path . '/web', $front_directory); + } + + file_put_contents($path . '/install.lock', 1); + } + + /** + * Uninstall locally installed plug-ins. + */ + public function uninstallExtension(string $path): void + { + $info = $this->readExtensionInfo($path); + if (! file_exists($path . '/install.lock')) { + throw new \RuntimeException(sprintf('Plugin %s not installed, cannot be uninstalled', $path)); + } + $uninstallScript = $info['uninstallScript']; + $uninstallScriptInstance = ApplicationContext::getContainer()->make($uninstallScript); + $this->migrator->rollback([$path . '/Database/Migrations']); + + if (method_exists($uninstallScriptInstance, '__invoke')) { + $uninstallScriptInstance(); + } + if (file_exists($path . '/web')) { + $front_directory = $this->config['front_directory']; + if (! file_exists($front_directory)) { + throw new \RuntimeException('The front-end source code directory does not exist or does not have permission to write to it'); } - /** - * If the plugin has a front-end file, copy it. - */ - if (file_exists($path.'/web')){ - $front_directory = $this->config['front_directory']; - if (!file_exists($front_directory)){ - throw new \RuntimeException('The front-end source code directory does not exist or does not have permission to write to it'); - } - FileSystem::copy($path.'/web',$front_directory); + $finder = Finder::create() + ->files() + ->in($path . '/web'); + foreach ($finder as $file) { + /** + * @var \SplFileInfo $file + */ + $path = substr($file->getPath(), $path); + var_dump($path); } - }); + } + FileSystem::delete($path . '/install.lock'); } /** diff --git a/src/app-store/src/Utils/FileSystemUtils.php b/src/app-store/src/Utils/FileSystemUtils.php new file mode 100644 index 00000000..50d91a84 --- /dev/null +++ b/src/app-store/src/Utils/FileSystemUtils.php @@ -0,0 +1,48 @@ + Date: Mon, 19 Feb 2024 17:45:15 +0800 Subject: [PATCH 09/14] fix --- src/app-store/src/Service/Impl/AppStoreServiceImpl.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/app-store/src/Service/Impl/AppStoreServiceImpl.php b/src/app-store/src/Service/Impl/AppStoreServiceImpl.php index c980b08e..abacd1d8 100644 --- a/src/app-store/src/Service/Impl/AppStoreServiceImpl.php +++ b/src/app-store/src/Service/Impl/AppStoreServiceImpl.php @@ -180,6 +180,15 @@ public function installExtension(string $path): void if (! file_exists($front_directory)) { throw new \RuntimeException('The front-end source code directory does not exist or does not have permission to write to it'); } + $finder = Finder::create() + ->files() + ->in($path.'/web'); + foreach ($finder as $file){ + /** + * @var \SplFileInfo $file + */ + $filepath = $file->getPath(); + } // todo 整个web 目录移植。目前这个方法不行。因为卸载的时候恢复不了源文件 FileSystemUtils::copyDirectory($path . '/web', $front_directory); } From 06798e4dcd91ffe2b048d2cec1114f5c0733ae87 Mon Sep 17 00:00:00 2001 From: Death-Satan <49744633+Death-Satan@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:32:33 +0800 Subject: [PATCH 10/14] Unified plug-in operation class --- src/app-store/publish/mine-extension.php | 5 +- .../src/Abstracts/AbstractExtension.php | 68 ------ .../src/Annotation/MineExtension.php | 18 -- .../src/Command/ExtensionInstallCommand.php | 7 +- .../src/Command/ExtensionLocalListCommand.php | 3 +- .../src/Command/ExtensionUninstallCommand.php | 7 +- src/app-store/src/ConfigProvider.php | 21 +- .../src/Listener/AppStoreListener.php | 49 ---- src/app-store/src/Plugin.php | 218 +++++++++++++++++ src/app-store/src/Response/Extension.php | 69 ------ src/app-store/src/Service/AppStoreService.php | 20 -- .../src/Service/ExtensionManagerService.php | 18 -- .../src/Service/Impl/AppStoreServiceImpl.php | 180 +------------- .../src/Service/Impl/PluginServiceImpl.php | 223 ++++++++++++++++++ src/app-store/src/Service/PluginService.php | 42 ++++ 15 files changed, 517 insertions(+), 431 deletions(-) delete mode 100644 src/app-store/src/Abstracts/AbstractExtension.php delete mode 100644 src/app-store/src/Annotation/MineExtension.php delete mode 100644 src/app-store/src/Listener/AppStoreListener.php create mode 100644 src/app-store/src/Plugin.php delete mode 100644 src/app-store/src/Response/Extension.php delete mode 100644 src/app-store/src/Service/ExtensionManagerService.php create mode 100644 src/app-store/src/Service/Impl/PluginServiceImpl.php create mode 100644 src/app-store/src/Service/PluginService.php diff --git a/src/app-store/publish/mine-extension.php b/src/app-store/publish/mine-extension.php index be4cbd89..ac24b687 100644 --- a/src/app-store/publish/mine-extension.php +++ b/src/app-store/publish/mine-extension.php @@ -19,11 +19,12 @@ * * 是否开启扩展商店功能,生产环境建议禁用。默认随着 APP_DEBUG 环境开启关闭 */ - 'enable' => env('APP_DEBUG'), + 'enable' => env('APP_DEBUG',false), /* * MineAdmin */ 'access_token' => env('MINE_ACCESS_TOKEN'), + /* * The root directory where the front-end code resides. * @@ -41,7 +42,7 @@ */ 'bin' => 'composer', ], - 'npm' => [ + 'front-tool' => [ /* * Front-end package management execution tools Optional npm yarn pnpm, default npm is used * diff --git a/src/app-store/src/Abstracts/AbstractExtension.php b/src/app-store/src/Abstracts/AbstractExtension.php deleted file mode 100644 index a08f6b4f..00000000 --- a/src/app-store/src/Abstracts/AbstractExtension.php +++ /dev/null @@ -1,68 +0,0 @@ -output->error(sprintf('Plugin directory %s does not exist', $pluginPath)); return; } - $info = $this->appStoreService->readExtensionInfo($pluginPath); + $info = $this->pluginService->read($pluginPath); $headers = ['Extension name', 'author', 'description', 'homepage']; $rows[] = [ @@ -55,7 +56,7 @@ public function __invoke() $this->output->success('Installation has been successfully canceled'); return; } - $this->appStoreService->installExtension($pluginPath); + $this->pluginService->installExtension($pluginPath); $this->output->success(sprintf('Plugin %s installed successfully', $pluginPath)); } diff --git a/src/app-store/src/Command/ExtensionLocalListCommand.php b/src/app-store/src/Command/ExtensionLocalListCommand.php index 38721cbd..cd3a48ee 100644 --- a/src/app-store/src/Command/ExtensionLocalListCommand.php +++ b/src/app-store/src/Command/ExtensionLocalListCommand.php @@ -16,6 +16,7 @@ use Hyperf\Command\Command as Base; use Hyperf\Context\ApplicationContext; use Xmo\AppStore\Service\AppStoreService; +use Xmo\AppStore\Service\PluginService; #[Command] class ExtensionLocalListCommand extends Base @@ -26,7 +27,7 @@ class ExtensionLocalListCommand extends Base public function __invoke() { - $service = ApplicationContext::getContainer()->get(AppStoreService::class); + $service = ApplicationContext::getContainer()->get(PluginService::class); $list = $service->getLocalExtensions(); $headers = [ 'extensionName', 'description', 'author', 'homePage', 'status', diff --git a/src/app-store/src/Command/ExtensionUninstallCommand.php b/src/app-store/src/Command/ExtensionUninstallCommand.php index 38bccc6b..ebbff424 100644 --- a/src/app-store/src/Command/ExtensionUninstallCommand.php +++ b/src/app-store/src/Command/ExtensionUninstallCommand.php @@ -17,6 +17,7 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Xmo\AppStore\Service\AppStoreService; +use Xmo\AppStore\Service\PluginService; #[Command] class ExtensionUninstallCommand extends Base @@ -26,7 +27,7 @@ class ExtensionUninstallCommand extends Base protected string $description = 'Uninstalling Plugin Commands'; public function __construct( - private readonly AppStoreService $appStoreService + private readonly PluginService $pluginService ) { parent::__construct(); } @@ -40,7 +41,7 @@ public function __invoke() $this->output->error(sprintf('Plugin directory %s does not exist', $pluginPath)); return; } - $info = $this->appStoreService->readExtensionInfo($pluginPath); + $info = $this->pluginService->read($pluginPath); $headers = ['Extension name', 'author', 'description', 'homepage']; $rows[] = [ @@ -55,7 +56,7 @@ public function __invoke() $this->output->success('Installation has been successfully canceled'); return; } - $this->appStoreService->uninstallExtension($pluginPath); + $this->pluginService->uninstallExtension($pluginPath); $this->output->success(sprintf('Plugin %s uninstalled successfully', $pluginPath)); } diff --git a/src/app-store/src/ConfigProvider.php b/src/app-store/src/ConfigProvider.php index c89e3a37..1f89b043 100644 --- a/src/app-store/src/ConfigProvider.php +++ b/src/app-store/src/ConfigProvider.php @@ -12,14 +12,20 @@ namespace Xmo\AppStore; +use Hyperf\Context\ApplicationContext; +use Hyperf\Contract\ConfigInterface; +use Symfony\Component\Finder\Finder; use Xmo\AppStore\Service\AppStoreService; use Xmo\AppStore\Service\Impl\AppStoreServiceImpl; +use Xmo\AppStore\Service\Impl\PluginServiceImpl; +use Xmo\AppStore\Service\PluginService; class ConfigProvider { public function __invoke() { - return [ + // Initial configuration + $initialConfig = [ // 合并到 config/autoload/annotations.php 文件 'annotations' => [ 'scan' => [ @@ -30,7 +36,20 @@ public function __invoke() ], 'dependencies' => [ AppStoreService::class => AppStoreServiceImpl::class, + PluginService::class => PluginServiceImpl::class ], ]; + + $mineJsonPaths = Plugin::getPluginJsonPaths(); + foreach ($mineJsonPaths as $jsonPath){ + if (file_exists($jsonPath->getPath().'/'.Plugin::INSTALL_LOCK_FILE)){ + $info = json_decode(file_get_contents($jsonPath->getRealPath()),true); + if (!empty($info['composer']['config'])){ + $provider = (new ($info['composer']['config']))(); + $initialConfig = array_merge_recursive($provider,$initialConfig); + } + } + } + return $initialConfig; } } diff --git a/src/app-store/src/Listener/AppStoreListener.php b/src/app-store/src/Listener/AppStoreListener.php deleted file mode 100644 index 215df212..00000000 --- a/src/app-store/src/Listener/AppStoreListener.php +++ /dev/null @@ -1,49 +0,0 @@ -getRealPath()), true, 512, JSON_THROW_ON_ERROR); + + if (file_exists($mine->getPath().'/'.self::INSTALL_LOCK_FILE)){ + $loader = Composer::getLoader(); + // psr-4 + if (!empty($mineInfo['composer']['psr-4'])){ + foreach ($mineInfo['composer']['psr-4'] as $namespace => $src){ + $src = realpath($mine->getPath().'/'.$src); + $loader->addPsr4($namespace,$src); + } + } + + // files + if (!empty($mineInfo['composer']['files'])){ + foreach ($mineInfo['composer']['files'] as $file){ + require_once $mine->getPath().'/'.$file; + } + } + + // classMap + if (!empty($mineInfo['composer']['classMap'])){ + $loader->addClassMap($mineInfo['composer']['classMap']); + } + + self::checkPlugin($mine); + } + } + } + + /** + * Check if the given file belongs to a qualified and valid plug-in + * @param \SplFileInfo $mineJson + * @return bool + */ + public static function checkPlugin(\SplFileInfo $mineJson): bool + { + $path = $mineJson->getRealPath(); + $info = json_decode(file_get_contents($path),true); + $rules = [ + 'name' => 'required|string', + 'description' => 'required|string', + 'author' => 'required|string', + 'composer' => 'required|array', + 'package' => 'array' + ]; + // todo 这里对插件信息进行验证 + return true; + } + + /** + * Get information about all local plugins + * @return SplFileInfo[] + */ + public static function getPluginJsonPaths(): array + { + if (self::$mineJsonPaths){ + return self::$mineJsonPaths; + } + $mines = Finder::create() + ->in(self::PLUGIN_PATH) + ->name('mine.json') + ->sortByChangedTime(); + foreach ($mines as $jsonFile){ + self::$mineJsonPaths[] = $jsonFile; + } + return self::$mineJsonPaths; + } + + /** + * Query plugin information based on a given catalog + * @param string $path + * @return mixed|null + */ + public static function read(string $path) + { + $jsonPaths = self::getPluginJsonPaths(); + foreach ($jsonPaths as $jsonPath){ + if ($jsonPath->getRelativePath() === $path){ + $info = json_decode(file_get_contents($jsonPath->getRealPath()),true); + $info['status'] = file_exists($jsonPath->getRealPath().'/'.self::INSTALL_LOCK_FILE); + return $info; + } + } + return null; + } + + /** + * Install the plugin according to the given directory + * @param string $path + * @return void + */ + public static function install(string $path): void + { + $info = self::read($path); + if ($info === null || !$info['status']){ + throw new \RuntimeException( + 'The given directory is not a valid plugin, + probably because it is already installed or the directory is not standardized' + ); + } + // Handling composer dependencies + if (!empty($info['composer']['require'])){ + $requires = $info['composer']['require']; + $composerBin = self::getConfig('composer.bin','composer'); + $checkCmd = System::exec(sprintf('%s --version',$composerBin)); + if (($checkCmd['code']??0) !== 0){ + throw new \RuntimeException(sprintf('Composer command error, details:%s',$checkCmd['output']??'--')); + } + $cmdBody = sprintf('cd %s &&',BASE_PATH); + $cmdBody .= sprintf('%s require ',$composerBin); + foreach ($requires as $package => $version){ + $cmdBody .= sprintf('%s:%s ',$package,$version); + } + $cmdBody .= sprintf('-vvv'); + $result = System::exec($cmdBody); + if ($result['code'] !== 0){ + throw new \RuntimeException(sprintf('Failed to execute composer require command, details:%s',$result['output']??'--')); + } + } + + $frontDirectory = self::getConfig('front_directory',BASE_PATH.'/web'); + + // Handling front-end dependency information + if (!empty($info['package']['dependencies'])){ + $frontBin = self::getConfig('front-tool'); + $dependencies = $info['package']['dependencies']; + $type = $frontBin['type'] ?? 'npm'; + $front = $frontBin['bin'] ?? 'npm'; + $checkCmd = System::exec(sprintf('%s ',$type)); + if ($checkCmd['code'] !== 0){ + throw new \RuntimeException(sprintf('An error occurred executing the command %s, details:%s',$type,$checkCmd['output'])); + } + $installCmd = match ($type){ + 'npm' => 'install', + 'yarn' => 'add', + 'pnpm' => 'add', + default => null + }; + if ($installCmd===null){ + throw new \RuntimeException('Front-end tool type is not recognizable npm,yarn,pnpm'); + } + $cmdBody = sprintf('cd %s && %s %s ',$frontDirectory,$front,$installCmd); + foreach ($dependencies as $package => $version){ + $cmdBody .= sprintf('%s@%s ',$package,$version); + } + $result = System::exec($cmdBody); + if ($result['code'] !== 0){ + throw new \RuntimeException(sprintf('Merge front-end dependency module error, details %s',$result['output']??'--')); + } + } + + // Handling database migration + $migrator = ApplicationContext::getContainer()->get(Migrator::class); + $seeder = ApplicationContext::getContainer()->get(Seed::class); + // Perform migration + $migrator->run($path.'/Database/Migrations'); + // Perform Data Filling + $seeder->run($path . '/Database/Seeders'); + + // If the plugin exists in the web directory, perform the migration of the front-end files + if (file_exists($path.'/web')){ + $finder = Finder::create() + ->files() + ->in($path.'/web'); + foreach ($finder as $file){ + /** + * @var SplFileInfo $file + */ + // todo 处理插件前端目录迁移到 后端目录 + } + } + } + + public static function getConfig(string $key,mixed $default = null): mixed + { + return ApplicationContext::getContainer() + ->get(ConfigInterface::class) + ->get('mine-extension.'.$key,$default); + } + + +} \ No newline at end of file diff --git a/src/app-store/src/Response/Extension.php b/src/app-store/src/Response/Extension.php deleted file mode 100644 index 2ecd9747..00000000 --- a/src/app-store/src/Response/Extension.php +++ /dev/null @@ -1,69 +0,0 @@ -client = $clientFactory->create([ 'base_uri' => 'https://www.mineadmin.com/server/store/', @@ -69,167 +67,6 @@ public function request(string $uri, array $data = []): array return $result; } - /** - * Get all locally extensions. - * @throws \JsonException - */ - public function getLocalExtensions(): array - { - $extensionPath = $this->getLocalExtensionsPath(); - if (! is_dir($extensionPath)) { - return []; - } - $finder = Finder::create() - ->files() - ->name('mine.json') - ->in($extensionPath); - if (! $finder->hasResults()) { - return []; - } - $result = []; - foreach ($finder as $file) { - $path = $file->getPath(); - /* - * @var \SplFileInfo $file - */ - $info = $this->readExtensionInfo($path); - $info['status'] = file_exists($path . '/install.lock'); - $result[] = $info; - } - return $result; - } - - /** - * Check if the given parameter is a valid configuration item. - */ - public function checkPlugin(array $info): bool - { - $checkOption = [ - 'name', 'description', 'author', 'require', 'description', 'dependencies', - 'installScript', 'uninstallScript', - ]; - foreach ($checkOption as $option) { - // Checking mine.json configuration - if (! array_key_exists($option, $info)) { - $this->throwCheckExtension($info['name'] ?? '--'); - } - $installScript = $info['installScript']; - $uninstallScript = $info['uninstallScript']; - if (! class_exists($installScript) || ! class_exists($uninstallScript)) { - throw new \RuntimeException( - sprintf('Installation and uninstallation scripts configured with the extension %s do not take effect and are not valid classes.', $info['name'] ?? '--') - ); - } - } - return true; - } - - /** - * Get local plugin directory. - */ - public function getLocalExtensionsPath(): string - { - return $this->getBasePath() . DIRECTORY_SEPARATOR . $this->getExtensionDirectory(); - } - - /** - * Read the specified directory to get the extension details. - * @throws \JsonException - */ - public function readExtensionInfo(string $path): array - { - $extensionJson = $path . DIRECTORY_SEPARATOR . 'mine.json'; - if (! file_exists($extensionJson)) { - throw new \RuntimeException(sprintf('The catalog %s is not a valid mine extension,because it\'s missing the necessary mine.json file.', $path)); - } - $info = json_decode(file_get_contents($extensionJson), true); - if (json_last_error() !== JSON_ERROR_NONE) { - throw new \RuntimeException(sprintf('Error reading mine.json file in %s,' . json_last_error_msg(), $path)); - } - $this->checkPlugin($info); - return $info; - } - - /** - * Installation of local plug-ins. - */ - public function installExtension(string $path): void - { - $info = $this->readExtensionInfo($path); - if (file_exists($path . '/install.lock')) { - throw new \RuntimeException(sprintf('The given directory %s is the directory where the plugin has already been installed.', $path)); - } - $installScript = $info['installScript']; - $installScriptInstance = ApplicationContext::getContainer()->make($installScript); - - /* - * The seeder and databases of the directory where the plugin is located are the data migration directories. - * The . /web directory directly into the specified front-end source code directory. - * and execute the plugin's installation script afterwards - */ - $this->migrator->run([$path . '/Database/Migrations']); - $this->seed->run([$path . '/Database/Seeders']); - if (method_exists($installScriptInstance, '__invoke')) { - $installScriptInstance(); - } - /* - * If the plugin has a front-end file, copy it. - */ - if (file_exists($path . '/web')) { - $front_directory = $this->config['front_directory']; - if (! file_exists($front_directory)) { - throw new \RuntimeException('The front-end source code directory does not exist or does not have permission to write to it'); - } - $finder = Finder::create() - ->files() - ->in($path.'/web'); - foreach ($finder as $file){ - /** - * @var \SplFileInfo $file - */ - $filepath = $file->getPath(); - } - // todo 整个web 目录移植。目前这个方法不行。因为卸载的时候恢复不了源文件 - FileSystemUtils::copyDirectory($path . '/web', $front_directory); - } - - file_put_contents($path . '/install.lock', 1); - } - - /** - * Uninstall locally installed plug-ins. - */ - public function uninstallExtension(string $path): void - { - $info = $this->readExtensionInfo($path); - if (! file_exists($path . '/install.lock')) { - throw new \RuntimeException(sprintf('Plugin %s not installed, cannot be uninstalled', $path)); - } - $uninstallScript = $info['uninstallScript']; - $uninstallScriptInstance = ApplicationContext::getContainer()->make($uninstallScript); - $this->migrator->rollback([$path . '/Database/Migrations']); - - if (method_exists($uninstallScriptInstance, '__invoke')) { - $uninstallScriptInstance(); - } - if (file_exists($path . '/web')) { - $front_directory = $this->config['front_directory']; - if (! file_exists($front_directory)) { - throw new \RuntimeException('The front-end source code directory does not exist or does not have permission to write to it'); - } - $finder = Finder::create() - ->files() - ->in($path . '/web'); - foreach ($finder as $file) { - /** - * @var \SplFileInfo $file - */ - $path = substr($file->getPath(), $path); - var_dump($path); - } - } - FileSystem::delete($path . '/install.lock'); - } /** * Get MineAdmin developer credentials. @@ -242,19 +79,4 @@ private function getAccessToken(): string } return (string) $accessToken; } - - private function throwCheckExtension(string $path): void - { - throw new \RuntimeException(sprintf('The catalog %s is not a valid mine extension package because the mine.json file it belongs to is missing key configurations such as `name` `author` `dependencies` `description` `require` `installScript` `uninstallScript` and so on.', $path)); - } - - private function getBasePath(): string - { - return BASE_PATH; - } - - private function getExtensionDirectory(): string - { - return 'plugin'; - } } diff --git a/src/app-store/src/Service/Impl/PluginServiceImpl.php b/src/app-store/src/Service/Impl/PluginServiceImpl.php new file mode 100644 index 00000000..bcbcae95 --- /dev/null +++ b/src/app-store/src/Service/Impl/PluginServiceImpl.php @@ -0,0 +1,223 @@ + 'required|string', + 'description' => 'required|string', + 'author' => 'required|string', + 'composer' => 'required|array', + 'package' => 'array', + 'installScript' => 'required|string', + 'uninstallScript' => 'required|string' + ]; + + private readonly array $config; + + + public function __construct( + private readonly ValidatorFactory $validatorFactory, + ConfigInterface $config, + private readonly Migrator $migrator, + private readonly Seed $seed + ){ + $this->config = $config->get('mine-extension',[]); + } + + /** + * @param string $path + * @return void + */ + public function register(string $path): void + { + $info = $this->read($path); + if (!$info['status']){ + self::$localPlugins[] = $info; + return; + } + $loader = Composer::getLoader(); + + // If it is already installed + $composer = $info['composer']; + if (!empty($composer['psr-4'])){ + // Register psr4 + foreach ($composer['psr-4'] as $namespace => $src){ + $loader->addPsr4($namespace,$path.'/'.$src); + } + } + + if (!empty($composer['class_map'])){ + // Register class_map + $loader->addClassMap($composer['class_map']); + } + } + + /** + * Reads the Mine plugin information through the given directory. + * And check the legitimacy of the plugin + * @param string $path + * @return array + */ + public function read(string $path): array + { + return Plugin::read($path); + } + + + /** + * Installation of local plug-ins. + */ + public function installExtension(string $path): void + { + $info = $this->read($path); + if ($info['status']) { + throw new \RuntimeException(sprintf('The given directory %s is the directory where the plugin has already been installed.', $path)); + } + $installScript = $info['installScript']; + $installScriptInstance = ApplicationContext::getContainer()->make($installScript); + + /* + * The seeder and databases of the directory where the plugin is located are the data migration directories. + * The . /web directory directly into the specified front-end source code directory. + * and execute the plugin's installation script afterwards + */ + $this->migrator->run([$path . '/Database/Migrations']); + $this->seed->run([$path . '/Database/Seeders']); + if (method_exists($installScriptInstance, '__invoke')) { + $installScriptInstance(); + } + /* + * If the plugin has a front-end file, copy it. + */ + if (file_exists($path . '/web')) { + $front_directory = $this->config['front_directory']; + if (! file_exists($front_directory)) { + throw new \RuntimeException('The front-end source code directory does not exist or does not have permission to write to it'); + } + $finder = Finder::create() + ->files() + ->in($path.'/web'); + foreach ($finder as $file){ + var_dump($file); + /** + * @var SplFileInfo $file + */ + $filepath = $file->getPath(); + } + // todo 整个web 目录移植。目前这个方法不行。因为卸载的时候恢复不了源文件 + FileSystemUtils::copyDirectory($path . '/web', $front_directory); + } + + file_put_contents($path . '/install.lock', 1); + } + + /** + * Get all locally extensions. + * @throws \JsonException + */ + public function getLocalExtensions(): array + { + $extensionPath = $this->getLocalExtensionsPath(); + if (! is_dir($extensionPath)) { + return []; + } + $finder = Finder::create() + ->files() + ->name('mine.json') + ->in($extensionPath); + if (! $finder->hasResults()) { + return []; + } + $result = []; + foreach ($finder as $file) { + $path = $file->getPath(); + /** + * @var SplFileInfo $file + */ + $info = $this->read($path); + $result[] = $info; + } + return $result; + } + + /** + * Uninstall locally installed plug-ins. + */ + public function uninstallExtension(string $path): void + { + $info = $this->read($path); + if (! file_exists($path . '/install.lock')) { + throw new \RuntimeException(sprintf('Plugin %s not installed, cannot be uninstalled', $path)); + } + $uninstallScript = $info['uninstallScript']; + $uninstallScriptInstance = ApplicationContext::getContainer()->make($uninstallScript); + $this->migrator->rollback([$path . '/Database/Migrations']); + + if (method_exists($uninstallScriptInstance, '__invoke')) { + $uninstallScriptInstance(); + } + if (file_exists($path . '/web')) { + $front_directory = $this->config['front_directory']; + if (! file_exists($front_directory)) { + throw new \RuntimeException('The front-end source code directory does not exist or does not have permission to write to it'); + } + $finder = Finder::create() + ->files() + ->in($path . '/web'); + foreach ($finder as $file) { + /** + * @var \SplFileInfo $file + */ + $path = substr($file->getPath(), $path); + var_dump($path); + } + } + FileSystem::delete($path . '/install.lock'); + } + + + /** + * Get local plugin directory. + */ + public function getLocalExtensionsPath(): string + { + return $this->getBasePath() . DIRECTORY_SEPARATOR . $this->getExtensionDirectory(); + } + + private function getBasePath(): string + { + return BASE_PATH; + } + + private function getExtensionDirectory(): string + { + return 'plugin'; + } +} \ No newline at end of file diff --git a/src/app-store/src/Service/PluginService.php b/src/app-store/src/Service/PluginService.php new file mode 100644 index 00000000..b21b7c3f --- /dev/null +++ b/src/app-store/src/Service/PluginService.php @@ -0,0 +1,42 @@ + Date: Fri, 23 Feb 2024 17:14:24 +0800 Subject: [PATCH 11/14] code style --- src/app-store/composer.json | 2 - src/app-store/publish/mine-extension.php | 2 +- .../src/Command/ExtensionInstallCommand.php | 13 +- .../src/Command/ExtensionLocalListCommand.php | 11 +- .../src/Command/ExtensionUninstallCommand.php | 6 +- src/app-store/src/ConfigProvider.php | 17 +- src/app-store/src/Plugin.php | 256 ++++++++++++------ src/app-store/src/Service/AppStoreService.php | 2 - .../src/Service/Impl/AppStoreServiceImpl.php | 8 - .../src/Service/Impl/PluginServiceImpl.php | 228 +--------------- src/app-store/src/Service/PluginService.php | 49 +--- src/app-store/src/Utils/FileSystemUtils.php | 53 ++-- 12 files changed, 248 insertions(+), 399 deletions(-) diff --git a/src/app-store/composer.json b/src/app-store/composer.json index 6105d869..ab9e7156 100644 --- a/src/app-store/composer.json +++ b/src/app-store/composer.json @@ -3,8 +3,6 @@ "description": "Mineadmin 2.0 AppStore Extension", "type": "library", "require": { - "xmo/mine-core": "v2.0.x-dev", - "xmo/mine-service": "v2.0.x-dev", "hyperf/translation": "~3.1.0" }, "require-dev": { diff --git a/src/app-store/publish/mine-extension.php b/src/app-store/publish/mine-extension.php index ac24b687..52062ca9 100644 --- a/src/app-store/publish/mine-extension.php +++ b/src/app-store/publish/mine-extension.php @@ -19,7 +19,7 @@ * * 是否开启扩展商店功能,生产环境建议禁用。默认随着 APP_DEBUG 环境开启关闭 */ - 'enable' => env('APP_DEBUG',false), + 'enable' => env('APP_DEBUG', false), /* * MineAdmin */ diff --git a/src/app-store/src/Command/ExtensionInstallCommand.php b/src/app-store/src/Command/ExtensionInstallCommand.php index 670780dc..4a19874a 100644 --- a/src/app-store/src/Command/ExtensionInstallCommand.php +++ b/src/app-store/src/Command/ExtensionInstallCommand.php @@ -16,7 +16,7 @@ use Hyperf\Command\Command as Base; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; -use Xmo\AppStore\Service\AppStoreService; +use Xmo\AppStore\Plugin; use Xmo\AppStore\Service\PluginService; #[Command] @@ -36,12 +36,7 @@ public function __invoke() { $path = $this->input->getArgument('path'); $yes = $this->input->getOption('yes'); - $pluginPath = BASE_PATH . '/plugin/' . $path; - if (! file_exists($pluginPath)) { - $this->output->error(sprintf('Plugin directory %s does not exist', $pluginPath)); - return; - } - $info = $this->pluginService->read($pluginPath); + $info = Plugin::read($path); $headers = ['Extension name', 'author', 'description', 'homepage']; $rows[] = [ @@ -56,8 +51,8 @@ public function __invoke() $this->output->success('Installation has been successfully canceled'); return; } - $this->pluginService->installExtension($pluginPath); - $this->output->success(sprintf('Plugin %s installed successfully', $pluginPath)); + Plugin::install($path); + $this->output->success(sprintf('Plugin %s installed successfully', $path)); } protected function configure() diff --git a/src/app-store/src/Command/ExtensionLocalListCommand.php b/src/app-store/src/Command/ExtensionLocalListCommand.php index cd3a48ee..5661dd7f 100644 --- a/src/app-store/src/Command/ExtensionLocalListCommand.php +++ b/src/app-store/src/Command/ExtensionLocalListCommand.php @@ -14,9 +14,7 @@ use Hyperf\Command\Annotation\Command; use Hyperf\Command\Command as Base; -use Hyperf\Context\ApplicationContext; -use Xmo\AppStore\Service\AppStoreService; -use Xmo\AppStore\Service\PluginService; +use Xmo\AppStore\Plugin; #[Command] class ExtensionLocalListCommand extends Base @@ -27,13 +25,14 @@ class ExtensionLocalListCommand extends Base public function __invoke() { - $service = ApplicationContext::getContainer()->get(PluginService::class); - $list = $service->getLocalExtensions(); + $list = Plugin::getPluginJsonPaths(); + $headers = [ 'extensionName', 'description', 'author', 'homePage', 'status', ]; $rows = []; - foreach ($list as $info) { + foreach ($list as $splFileInfo) { + $info = Plugin::read($splFileInfo->getPath()); $current = []; $current[] = $info['name']; $current[] = $info['description']; diff --git a/src/app-store/src/Command/ExtensionUninstallCommand.php b/src/app-store/src/Command/ExtensionUninstallCommand.php index ebbff424..c9ec1ca5 100644 --- a/src/app-store/src/Command/ExtensionUninstallCommand.php +++ b/src/app-store/src/Command/ExtensionUninstallCommand.php @@ -16,7 +16,7 @@ use Hyperf\Command\Command as Base; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; -use Xmo\AppStore\Service\AppStoreService; +use Xmo\AppStore\Plugin; use Xmo\AppStore\Service\PluginService; #[Command] @@ -41,7 +41,7 @@ public function __invoke() $this->output->error(sprintf('Plugin directory %s does not exist', $pluginPath)); return; } - $info = $this->pluginService->read($pluginPath); + $info = Plugin::read($path); $headers = ['Extension name', 'author', 'description', 'homepage']; $rows[] = [ @@ -56,7 +56,7 @@ public function __invoke() $this->output->success('Installation has been successfully canceled'); return; } - $this->pluginService->uninstallExtension($pluginPath); + Plugin::uninstall($path); $this->output->success(sprintf('Plugin %s uninstalled successfully', $pluginPath)); } diff --git a/src/app-store/src/ConfigProvider.php b/src/app-store/src/ConfigProvider.php index 1f89b043..90053c3a 100644 --- a/src/app-store/src/ConfigProvider.php +++ b/src/app-store/src/ConfigProvider.php @@ -12,9 +12,6 @@ namespace Xmo\AppStore; -use Hyperf\Context\ApplicationContext; -use Hyperf\Contract\ConfigInterface; -use Symfony\Component\Finder\Finder; use Xmo\AppStore\Service\AppStoreService; use Xmo\AppStore\Service\Impl\AppStoreServiceImpl; use Xmo\AppStore\Service\Impl\PluginServiceImpl; @@ -25,7 +22,7 @@ class ConfigProvider public function __invoke() { // Initial configuration - $initialConfig = [ + $initialConfig = [ // 合并到 config/autoload/annotations.php 文件 'annotations' => [ 'scan' => [ @@ -36,17 +33,17 @@ public function __invoke() ], 'dependencies' => [ AppStoreService::class => AppStoreServiceImpl::class, - PluginService::class => PluginServiceImpl::class + PluginService::class => PluginServiceImpl::class, ], ]; $mineJsonPaths = Plugin::getPluginJsonPaths(); - foreach ($mineJsonPaths as $jsonPath){ - if (file_exists($jsonPath->getPath().'/'.Plugin::INSTALL_LOCK_FILE)){ - $info = json_decode(file_get_contents($jsonPath->getRealPath()),true); - if (!empty($info['composer']['config'])){ + foreach ($mineJsonPaths as $jsonPath) { + if (file_exists($jsonPath->getPath() . '/' . Plugin::INSTALL_LOCK_FILE)) { + $info = json_decode(file_get_contents($jsonPath->getRealPath()), true); + if (! empty($info['composer']['config'])) { $provider = (new ($info['composer']['config']))(); - $initialConfig = array_merge_recursive($provider,$initialConfig); + $initialConfig = array_merge_recursive($provider, $initialConfig); } } } diff --git a/src/app-store/src/Plugin.php b/src/app-store/src/Plugin.php index 34819fd5..961f20ec 100644 --- a/src/app-store/src/Plugin.php +++ b/src/app-store/src/Plugin.php @@ -1,29 +1,39 @@ getRealPath()), true, 512, JSON_THROW_ON_ERROR); - if (file_exists($mine->getPath().'/'.self::INSTALL_LOCK_FILE)){ + if (file_exists($mine->getPath() . '/' . self::INSTALL_LOCK_FILE)) { $loader = Composer::getLoader(); // psr-4 - if (!empty($mineInfo['composer']['psr-4'])){ - foreach ($mineInfo['composer']['psr-4'] as $namespace => $src){ - $src = realpath($mine->getPath().'/'.$src); - $loader->addPsr4($namespace,$src); + if (! empty($mineInfo['composer']['psr-4'])) { + foreach ($mineInfo['composer']['psr-4'] as $namespace => $src) { + $src = realpath($mine->getPath() . '/' . $src); + $loader->addPsr4($namespace, $src); } } // files - if (!empty($mineInfo['composer']['files'])){ - foreach ($mineInfo['composer']['files'] as $file){ - require_once $mine->getPath().'/'.$file; + if (! empty($mineInfo['composer']['files'])) { + foreach ($mineInfo['composer']['files'] as $file) { + require_once $mine->getPath() . '/' . $file; } } // classMap - if (!empty($mineInfo['composer']['classMap'])){ + if (! empty($mineInfo['composer']['classMap'])) { $loader->addClassMap($mineInfo['composer']['classMap']); } @@ -64,56 +74,53 @@ public static function init(): void } /** - * Check if the given file belongs to a qualified and valid plug-in - * @param \SplFileInfo $mineJson - * @return bool + * Check if the given file belongs to a qualified and valid plug-in. */ public static function checkPlugin(\SplFileInfo $mineJson): bool { $path = $mineJson->getRealPath(); - $info = json_decode(file_get_contents($path),true); + $info = json_decode(file_get_contents($path), true); $rules = [ - 'name' => 'required|string', - 'description' => 'required|string', - 'author' => 'required|string', - 'composer' => 'required|array', - 'package' => 'array' + 'name' => 'required|string', + 'description' => 'required|string', + 'author' => 'required|string', + 'composer' => 'required|array', + 'package' => 'array', ]; // todo 这里对插件信息进行验证 return true; } /** - * Get information about all local plugins + * Get information about all local plugins. * @return SplFileInfo[] */ public static function getPluginJsonPaths(): array { - if (self::$mineJsonPaths){ + if (self::$mineJsonPaths) { return self::$mineJsonPaths; } $mines = Finder::create() ->in(self::PLUGIN_PATH) ->name('mine.json') ->sortByChangedTime(); - foreach ($mines as $jsonFile){ + foreach ($mines as $jsonFile) { self::$mineJsonPaths[] = $jsonFile; } return self::$mineJsonPaths; } /** - * Query plugin information based on a given catalog - * @param string $path - * @return mixed|null + * Query plugin information based on a given catalog. + * @return null|mixed */ public static function read(string $path) { $jsonPaths = self::getPluginJsonPaths(); - foreach ($jsonPaths as $jsonPath){ - if ($jsonPath->getRelativePath() === $path){ - $info = json_decode(file_get_contents($jsonPath->getRealPath()),true); - $info['status'] = file_exists($jsonPath->getRealPath().'/'.self::INSTALL_LOCK_FILE); + foreach ($jsonPaths as $jsonPath) { + if ($jsonPath->getRelativePath() === $path) { + $info = json_decode(file_get_contents($jsonPath->getRealPath()), true); + $info['status'] = file_exists($jsonPath->getRealPath() . '/' . self::INSTALL_LOCK_FILE); return $info; } } @@ -121,98 +128,195 @@ public static function read(string $path) } /** - * Install the plugin according to the given directory - * @param string $path - * @return void + * Install the plugin according to the given directory. */ public static function install(string $path): void { $info = self::read($path); - if ($info === null || !$info['status']){ + $pluginPath = self::PLUGIN_PATH . '/' . $path; + if ($info === null || $info['status']) { throw new \RuntimeException( 'The given directory is not a valid plugin, probably because it is already installed or the directory is not standardized' ); } // Handling composer dependencies - if (!empty($info['composer']['require'])){ + if (! empty($info['composer']['require'])) { $requires = $info['composer']['require']; - $composerBin = self::getConfig('composer.bin','composer'); - $checkCmd = System::exec(sprintf('%s --version',$composerBin)); - if (($checkCmd['code']??0) !== 0){ - throw new \RuntimeException(sprintf('Composer command error, details:%s',$checkCmd['output']??'--')); + $composerBin = self::getConfig('composer.bin', 'composer'); + $checkCmd = System::exec(sprintf('%s --version', $composerBin)); + if (($checkCmd['code'] ?? 0) !== 0) { + throw new \RuntimeException(sprintf('Composer command error, details:%s', $checkCmd['output'] ?? '--')); } - $cmdBody = sprintf('cd %s &&',BASE_PATH); - $cmdBody .= sprintf('%s require ',$composerBin); - foreach ($requires as $package => $version){ - $cmdBody .= sprintf('%s:%s ',$package,$version); + $cmdBody = sprintf('cd %s &&', BASE_PATH); + $cmdBody .= sprintf('%s require ', $composerBin); + foreach ($requires as $package => $version) { + if (InstalledVersions::isInstalled($package)) { + throw new \RuntimeException(sprintf('Plugin %s depends on %s, but detects package installed', $info['name'], $package)); + } + $cmdBody .= sprintf('%s:%s ', $package, $version); } $cmdBody .= sprintf('-vvv'); $result = System::exec($cmdBody); - if ($result['code'] !== 0){ - throw new \RuntimeException(sprintf('Failed to execute composer require command, details:%s',$result['output']??'--')); + if ($result['code'] !== 0) { + throw new \RuntimeException(sprintf('Failed to execute composer require command, details:%s', $result['output'] ?? '--')); } } - $frontDirectory = self::getConfig('front_directory',BASE_PATH.'/web'); + $frontDirectory = self::getConfig('front_directory', BASE_PATH . '/web'); // Handling front-end dependency information - if (!empty($info['package']['dependencies'])){ + if (! empty($info['package']['dependencies'])) { $frontBin = self::getConfig('front-tool'); $dependencies = $info['package']['dependencies']; + if (! file_exists($frontDirectory . '/package.json')) { + throw new \RuntimeException(sprintf('Specified frontend directory %s package.json not found', $frontDirectory)); + } + $packageJson = json_decode(file_get_contents($frontDirectory . '/package.json'), true); + $frontDependencies = array_keys($packageJson['dependencies'] ?? []); $type = $frontBin['type'] ?? 'npm'; $front = $frontBin['bin'] ?? 'npm'; - $checkCmd = System::exec(sprintf('%s ',$type)); - if ($checkCmd['code'] !== 0){ - throw new \RuntimeException(sprintf('An error occurred executing the command %s, details:%s',$type,$checkCmd['output'])); - } - $installCmd = match ($type){ - 'npm' => 'install', - 'yarn' => 'add', - 'pnpm' => 'add', + $checkCmd = System::exec(sprintf('%s ', $type)); + if ($checkCmd['code'] !== 0) { + throw new \RuntimeException(sprintf('An error occurred executing the command %s, details:%s', $type, $checkCmd['output'])); + } + $installCmd = match ($type) { + 'npm', 'pnpm' => 'install', + 'yarn' => 'add', default => null }; - if ($installCmd===null){ + if ($installCmd === null) { throw new \RuntimeException('Front-end tool type is not recognizable npm,yarn,pnpm'); } - $cmdBody = sprintf('cd %s && %s %s ',$frontDirectory,$front,$installCmd); - foreach ($dependencies as $package => $version){ - $cmdBody .= sprintf('%s@%s ',$package,$version); + $cmdBody = sprintf('cd %s && %s %s ', $frontDirectory, $front, $installCmd); + foreach ($dependencies as $package => $version) { + if (in_array($package, $frontDependencies, true)) { + throw new \RuntimeException(sprintf('Plugin %s depends on %s, but the dependency already exists in the project.', $info['name'], $package)); + } + $cmdBody .= sprintf('%s@%s ', $package, $version); } $result = System::exec($cmdBody); - if ($result['code'] !== 0){ - throw new \RuntimeException(sprintf('Merge front-end dependency module error, details %s',$result['output']??'--')); + if ($result['code'] !== 0) { + throw new \RuntimeException(sprintf('Merge front-end dependency module error, details %s', $result['output'] ?? '--')); } } // Handling database migration $migrator = ApplicationContext::getContainer()->get(Migrator::class); $seeder = ApplicationContext::getContainer()->get(Seed::class); + // Perform migration - $migrator->run($path.'/Database/Migrations'); + $migrator->run($pluginPath . '/Database/Migrations'); // Perform Data Filling - $seeder->run($path . '/Database/Seeders'); + $seeder->run($pluginPath . '/Database/Seeders'); + // If the plugin exists in the web directory, perform the migration of the front-end files + if (file_exists($pluginPath . '/web')) { + $finder = Finder::create() + ->files() + ->in($pluginPath . '/web'); + foreach ($finder as $file) { + /** + * @var SplFileInfo $file + */ + $relativeFilePath = $file->getRelativePathname(); + FileSystemUtils::copy($pluginPath . $relativeFilePath, $frontDirectory . $relativeFilePath); + } + } + } + + public static function uninstall(string $path): void + { + $info = self::read($path); + $pluginPath = self::PLUGIN_PATH . '/' . $path; + if ($info === null || ! $info['status']) { + throw new \RuntimeException( + 'No installation behavior was detected for this plugin, and uninstallation could not be performed' + ); + } + if (! empty($info['composer']['require'])) { + $requires = $info['composer']['require']; + $composerBin = self::getConfig('composer.bin', 'composer'); + $checkCmd = System::exec(sprintf('%s --version', $composerBin)); + if (($checkCmd['code'] ?? 0) !== 0) { + throw new \RuntimeException(sprintf('Composer command error, details:%s', $checkCmd['output'] ?? '--')); + } + $cmdBody = sprintf('cd %s &&', BASE_PATH); + $cmdBody .= sprintf('%s remove ', $composerBin); + foreach ($requires as $package => $version) { + if (! InstalledVersions::isInstalled($package)) { + throw new \RuntimeException(sprintf('Plugin %s depends on %s, But the package does not exist in the current environment', $info['name'], $package)); + } + $cmdBody .= sprintf('%s:%s ', $package, $version); + } + $cmdBody .= sprintf('-vvv'); + $result = System::exec($cmdBody); + if ($result['code'] !== 0) { + throw new \RuntimeException(sprintf('Failed to execute composer require command, details:%s', $result['output'] ?? '--')); + } + } + + $frontDirectory = self::getConfig('front_directory', BASE_PATH . '/web'); + + // Handling front-end dependency information + if (! empty($info['package']['dependencies'])) { + $frontBin = self::getConfig('front-tool'); + $dependencies = $info['package']['dependencies']; + if (! file_exists($frontDirectory . '/package.json')) { + throw new \RuntimeException(sprintf('Specified frontend directory %s package.json not found', $frontDirectory)); + } + $packageJson = json_decode(file_get_contents($frontDirectory . '/package.json'), true); + $frontDependencies = array_keys($packageJson['dependencies'] ?? []); + $type = $frontBin['type'] ?? 'npm'; + $front = $frontBin['bin'] ?? 'npm'; + $checkCmd = System::exec(sprintf('%s ', $type)); + if ($checkCmd['code'] !== 0) { + throw new \RuntimeException(sprintf('An error occurred executing the command %s, details:%s', $type, $checkCmd['output'])); + } + $installCmd = match ($type) { + 'npm', 'pnpm' => 'uninstall', + 'yarn' => 'remove', + default => null + }; + if ($installCmd === null) { + throw new \RuntimeException('Front-end tool type is not recognizable npm,yarn,pnpm'); + } + $cmdBody = sprintf('cd %s && %s %s ', $frontDirectory, $front, $installCmd); + foreach ($dependencies as $package => $version) { + if (! in_array($package, $frontDependencies, true)) { + throw new \RuntimeException(sprintf('Plugin %s depends on %s,But the dependency information is not found in this project', $info['name'], $package)); + } + $cmdBody .= sprintf('%s@%s ', $package, $version); + } + $result = System::exec($cmdBody); + if ($result['code'] !== 0) { + throw new \RuntimeException(sprintf('Merge front-end dependency module error, details %s', $result['output'] ?? '--')); + } + } + + // Handling database migration + $migrator = ApplicationContext::getContainer()->get(Migrator::class); + // Perform migration rollback + $migrator->rollback($pluginPath . '/Database/Migrations'); // If the plugin exists in the web directory, perform the migration of the front-end files - if (file_exists($path.'/web')){ + if (file_exists($pluginPath . '/web')) { $finder = Finder::create() ->files() - ->in($path.'/web'); - foreach ($finder as $file){ + ->in($pluginPath . '/web'); + foreach ($finder as $file) { /** * @var SplFileInfo $file */ - // todo 处理插件前端目录迁移到 后端目录 + $relativeFilePath = $file->getRelativePathname(); + FileSystemUtils::recovery($relativeFilePath, $frontDirectory); } } } - public static function getConfig(string $key,mixed $default = null): mixed + public static function getConfig(string $key, mixed $default = null): mixed { return ApplicationContext::getContainer() ->get(ConfigInterface::class) - ->get('mine-extension.'.$key,$default); + ->get('mine-extension.' . $key, $default); } - - -} \ No newline at end of file +} diff --git a/src/app-store/src/Service/AppStoreService.php b/src/app-store/src/Service/AppStoreService.php index 7f29816b..d90d6560 100644 --- a/src/app-store/src/Service/AppStoreService.php +++ b/src/app-store/src/Service/AppStoreService.php @@ -21,6 +21,4 @@ interface AppStoreService * @throws \JsonException */ public function request(string $uri, array $data = []): array; - - } diff --git a/src/app-store/src/Service/Impl/AppStoreServiceImpl.php b/src/app-store/src/Service/Impl/AppStoreServiceImpl.php index 849134fc..3814416f 100644 --- a/src/app-store/src/Service/Impl/AppStoreServiceImpl.php +++ b/src/app-store/src/Service/Impl/AppStoreServiceImpl.php @@ -14,16 +14,9 @@ use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException; -use Hyperf\Context\ApplicationContext; use Hyperf\Contract\ConfigInterface; -use Hyperf\Database\Migrations\Migrator; -use Hyperf\Database\Seeders\Seed; -use Hyperf\Database\Seeders\Seeder; use Hyperf\Guzzle\ClientFactory; -use Nette\Utils\FileSystem; -use Symfony\Component\Finder\Finder; use Xmo\AppStore\Service\AppStoreService; -use Xmo\AppStore\Utils\FileSystemUtils; use function Hyperf\Support\env; use function Hyperf\Translation\trans; @@ -67,7 +60,6 @@ public function request(string $uri, array $data = []): array return $result; } - /** * Get MineAdmin developer credentials. */ diff --git a/src/app-store/src/Service/Impl/PluginServiceImpl.php b/src/app-store/src/Service/Impl/PluginServiceImpl.php index bcbcae95..9295d837 100644 --- a/src/app-store/src/Service/Impl/PluginServiceImpl.php +++ b/src/app-store/src/Service/Impl/PluginServiceImpl.php @@ -1,223 +1,17 @@ 'required|string', - 'description' => 'required|string', - 'author' => 'required|string', - 'composer' => 'required|array', - 'package' => 'array', - 'installScript' => 'required|string', - 'uninstallScript' => 'required|string' - ]; - - private readonly array $config; - - - public function __construct( - private readonly ValidatorFactory $validatorFactory, - ConfigInterface $config, - private readonly Migrator $migrator, - private readonly Seed $seed - ){ - $this->config = $config->get('mine-extension',[]); - } - - /** - * @param string $path - * @return void - */ - public function register(string $path): void - { - $info = $this->read($path); - if (!$info['status']){ - self::$localPlugins[] = $info; - return; - } - $loader = Composer::getLoader(); - - // If it is already installed - $composer = $info['composer']; - if (!empty($composer['psr-4'])){ - // Register psr4 - foreach ($composer['psr-4'] as $namespace => $src){ - $loader->addPsr4($namespace,$path.'/'.$src); - } - } - - if (!empty($composer['class_map'])){ - // Register class_map - $loader->addClassMap($composer['class_map']); - } - } - - /** - * Reads the Mine plugin information through the given directory. - * And check the legitimacy of the plugin - * @param string $path - * @return array - */ - public function read(string $path): array - { - return Plugin::read($path); - } - - - /** - * Installation of local plug-ins. - */ - public function installExtension(string $path): void - { - $info = $this->read($path); - if ($info['status']) { - throw new \RuntimeException(sprintf('The given directory %s is the directory where the plugin has already been installed.', $path)); - } - $installScript = $info['installScript']; - $installScriptInstance = ApplicationContext::getContainer()->make($installScript); - - /* - * The seeder and databases of the directory where the plugin is located are the data migration directories. - * The . /web directory directly into the specified front-end source code directory. - * and execute the plugin's installation script afterwards - */ - $this->migrator->run([$path . '/Database/Migrations']); - $this->seed->run([$path . '/Database/Seeders']); - if (method_exists($installScriptInstance, '__invoke')) { - $installScriptInstance(); - } - /* - * If the plugin has a front-end file, copy it. - */ - if (file_exists($path . '/web')) { - $front_directory = $this->config['front_directory']; - if (! file_exists($front_directory)) { - throw new \RuntimeException('The front-end source code directory does not exist or does not have permission to write to it'); - } - $finder = Finder::create() - ->files() - ->in($path.'/web'); - foreach ($finder as $file){ - var_dump($file); - /** - * @var SplFileInfo $file - */ - $filepath = $file->getPath(); - } - // todo 整个web 目录移植。目前这个方法不行。因为卸载的时候恢复不了源文件 - FileSystemUtils::copyDirectory($path . '/web', $front_directory); - } - - file_put_contents($path . '/install.lock', 1); - } - - /** - * Get all locally extensions. - * @throws \JsonException - */ - public function getLocalExtensions(): array - { - $extensionPath = $this->getLocalExtensionsPath(); - if (! is_dir($extensionPath)) { - return []; - } - $finder = Finder::create() - ->files() - ->name('mine.json') - ->in($extensionPath); - if (! $finder->hasResults()) { - return []; - } - $result = []; - foreach ($finder as $file) { - $path = $file->getPath(); - /** - * @var SplFileInfo $file - */ - $info = $this->read($path); - $result[] = $info; - } - return $result; - } - - /** - * Uninstall locally installed plug-ins. - */ - public function uninstallExtension(string $path): void - { - $info = $this->read($path); - if (! file_exists($path . '/install.lock')) { - throw new \RuntimeException(sprintf('Plugin %s not installed, cannot be uninstalled', $path)); - } - $uninstallScript = $info['uninstallScript']; - $uninstallScriptInstance = ApplicationContext::getContainer()->make($uninstallScript); - $this->migrator->rollback([$path . '/Database/Migrations']); - - if (method_exists($uninstallScriptInstance, '__invoke')) { - $uninstallScriptInstance(); - } - if (file_exists($path . '/web')) { - $front_directory = $this->config['front_directory']; - if (! file_exists($front_directory)) { - throw new \RuntimeException('The front-end source code directory does not exist or does not have permission to write to it'); - } - $finder = Finder::create() - ->files() - ->in($path . '/web'); - foreach ($finder as $file) { - /** - * @var \SplFileInfo $file - */ - $path = substr($file->getPath(), $path); - var_dump($path); - } - } - FileSystem::delete($path . '/install.lock'); - } - - - /** - * Get local plugin directory. - */ - public function getLocalExtensionsPath(): string - { - return $this->getBasePath() . DIRECTORY_SEPARATOR . $this->getExtensionDirectory(); - } - - private function getBasePath(): string - { - return BASE_PATH; - } - private function getExtensionDirectory(): string - { - return 'plugin'; - } -} \ No newline at end of file +class PluginServiceImpl implements PluginService {} diff --git a/src/app-store/src/Service/PluginService.php b/src/app-store/src/Service/PluginService.php index b21b7c3f..827386f5 100644 --- a/src/app-store/src/Service/PluginService.php +++ b/src/app-store/src/Service/PluginService.php @@ -1,42 +1,15 @@ Date: Sat, 24 Feb 2024 21:21:13 +0800 Subject: [PATCH 12/14] Improvement of the app store Enquiry, download, details --- composer.json | 1 + src/app-store/composer.json | 3 +- src/app-store/src/Service/AppStoreService.php | 15 +++++++ .../src/Service/Impl/AppStoreServiceImpl.php | 43 ++++++++++++++++++- 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index d74a403a..6373bae4 100644 --- a/composer.json +++ b/composer.json @@ -61,6 +61,7 @@ "ext-pdo_mysql": "*", "ext-redis": "*", "ext-swoole": ">=5.0", + "ext-zip": "*", "doctrine/dbal": "^3.1", "hyperf/amqp": "~3.1.0", "hyperf/async-queue": "~3.1.0", diff --git a/src/app-store/composer.json b/src/app-store/composer.json index ab9e7156..1edecafe 100644 --- a/src/app-store/composer.json +++ b/src/app-store/composer.json @@ -3,7 +3,8 @@ "description": "Mineadmin 2.0 AppStore Extension", "type": "library", "require": { - "hyperf/translation": "~3.1.0" + "hyperf/translation": "~3.1.0", + "ext-zip": "*" }, "require-dev": { "pestphp/pest": "^2.33" diff --git a/src/app-store/src/Service/AppStoreService.php b/src/app-store/src/Service/AppStoreService.php index d90d6560..63da04df 100644 --- a/src/app-store/src/Service/AppStoreService.php +++ b/src/app-store/src/Service/AppStoreService.php @@ -21,4 +21,19 @@ interface AppStoreService * @throws \JsonException */ public function request(string $uri, array $data = []): array; + + /** + * Download the specified plug-in to a local directory. + */ + public function download(string $plugin): bool; + + /** + * Get the details of the specified plugin. + */ + public function view(string $plugin): array; + + /** + * Get the list of remote plugins. + */ + public function list(array $params): array; } diff --git a/src/app-store/src/Service/Impl/AppStoreServiceImpl.php b/src/app-store/src/Service/Impl/AppStoreServiceImpl.php index 3814416f..924f6f5a 100644 --- a/src/app-store/src/Service/Impl/AppStoreServiceImpl.php +++ b/src/app-store/src/Service/Impl/AppStoreServiceImpl.php @@ -14,8 +14,10 @@ use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException; +use GuzzleHttp\RequestOptions; use Hyperf\Contract\ConfigInterface; use Hyperf\Guzzle\ClientFactory; +use Xmo\AppStore\Plugin; use Xmo\AppStore\Service\AppStoreService; use function Hyperf\Support\env; @@ -47,7 +49,7 @@ public function request(string $uri, array $data = []): array $response = $this->client->post($uri, [ 'json' => $data, 'headers' => [ - 'access-token' => $this->getAccessToken(), + 'Access-Token' => $this->getAccessToken(), ], ]); if ($response->getStatusCode() !== 200) { @@ -60,6 +62,45 @@ public function request(string $uri, array $data = []): array return $result; } + /** + * Get the list of remote plugins. + */ + public function list(array $params): array + { + return $this->request(__FUNCTION__, $params); + } + + /** + * Get the details of the specified plugin. + */ + public function view(string $plugin): array + { + return $this->request(__FUNCTION__, compact('plugin')); + } + + /** + * Download the specified plug-in to a local directory. + */ + public function download(string $plugin): bool + { + $downloadToken = $this->request(__FUNCTION__, compact('plugin'))[0] ?? ''; + $tmpFile = sys_get_temp_dir() . '/' . uniqid('mine', true) . '.zip'; + $tmpFileResource = fopen(sys_get_temp_dir() . '/' . uniqid('mine', true) . '.zip', 'wb+'); + $response = $this->client->get('download_file?token=' . $downloadToken, [ + RequestOptions::SINK => $tmpFileResource, + ]); + if ($response->getStatusCode() !== 200) { + throw new \RuntimeException('Failed to download plugin'); + } + $zip = new \ZipArchive(); + if ($zip->open($tmpFile) !== true) { + throw new \RuntimeException('Failed to open the zip file'); + } + $zip->extractTo(Plugin::PLUGIN_PATH); + $zip->close(); + return true; + } + /** * Get MineAdmin developer credentials. */ From c00c7baa8b2a661060bc428d6534523464166eac Mon Sep 17 00:00:00 2001 From: Death-Satan <49744633+Death-Satan@users.noreply.github.com> Date: Mon, 26 Feb 2024 00:01:24 +0800 Subject: [PATCH 13/14] Added commands for remote plugin download and remote plugin list. --- .../src/Command/ExtensionDownloadCommand.php | 30 ++++++++++++++ .../src/Command/ExtensionListCommand.php | 41 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/app-store/src/Command/ExtensionDownloadCommand.php create mode 100644 src/app-store/src/Command/ExtensionListCommand.php diff --git a/src/app-store/src/Command/ExtensionDownloadCommand.php b/src/app-store/src/Command/ExtensionDownloadCommand.php new file mode 100644 index 00000000..c3379cab --- /dev/null +++ b/src/app-store/src/Command/ExtensionDownloadCommand.php @@ -0,0 +1,30 @@ +addOption('name','n',InputOption::VALUE_REQUIRED,'Plug-in Name'); + } + + public function __invoke() + { + $name = $this->input->getOption('name'); + $appStoreService = ApplicationContext::getContainer()->get(AppStoreService::class); + $appStoreService->download($name); + $this->output->success('Plugin Downloaded Successfully'); + } +} \ No newline at end of file diff --git a/src/app-store/src/Command/ExtensionListCommand.php b/src/app-store/src/Command/ExtensionListCommand.php new file mode 100644 index 00000000..ba284151 --- /dev/null +++ b/src/app-store/src/Command/ExtensionListCommand.php @@ -0,0 +1,41 @@ +addOption('type','t',InputOption::VALUE_OPTIONAL,'Type of plugin to query'); + $this->addOption('title','title',InputOption::VALUE_OPTIONAL,'Plugin Title'); + } + + public function __invoke() + { + $params = []; + $params['type'] = $this->input->getOption('type'); + if (empty($params['type'])){ + $type = 'all'; + } + if ($title = $this->input->getOption('title')){ + $params['title'] = $title; + } + $appStoreService = ApplicationContext::getContainer()->get(AppStoreService::class); + $result = $appStoreService->list($params); + $headers = [ + 'extensionName', 'description', 'author', 'homePage', 'status', + ]; + $this->output->table($headers,$result); + } +} \ No newline at end of file From 60bc5b08cd0775bbd917570be0328d510e8094ed Mon Sep 17 00:00:00 2001 From: Death-Satan <49744633+Death-Satan@users.noreply.github.com> Date: Mon, 26 Feb 2024 00:02:54 +0800 Subject: [PATCH 14/14] Code Style --- .../src/Command/ExtensionDownloadCommand.php | 24 ++++++++++---- .../src/Command/ExtensionListCommand.php | 32 ++++++++++++------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/app-store/src/Command/ExtensionDownloadCommand.php b/src/app-store/src/Command/ExtensionDownloadCommand.php index c3379cab..4c2907a0 100644 --- a/src/app-store/src/Command/ExtensionDownloadCommand.php +++ b/src/app-store/src/Command/ExtensionDownloadCommand.php @@ -1,9 +1,19 @@ addOption('name','n',InputOption::VALUE_REQUIRED,'Plug-in Name'); - } - public function __invoke() { $name = $this->input->getOption('name'); @@ -27,4 +32,9 @@ public function __invoke() $appStoreService->download($name); $this->output->success('Plugin Downloaded Successfully'); } -} \ No newline at end of file + + protected function configure() + { + $this->addOption('name', 'n', InputOption::VALUE_REQUIRED, 'Plug-in Name'); + } +} diff --git a/src/app-store/src/Command/ExtensionListCommand.php b/src/app-store/src/Command/ExtensionListCommand.php index ba284151..9641afc5 100644 --- a/src/app-store/src/Command/ExtensionListCommand.php +++ b/src/app-store/src/Command/ExtensionListCommand.php @@ -1,5 +1,15 @@ addOption('type','t',InputOption::VALUE_OPTIONAL,'Type of plugin to query'); - $this->addOption('title','title',InputOption::VALUE_OPTIONAL,'Plugin Title'); - } - public function __invoke() { $params = []; $params['type'] = $this->input->getOption('type'); - if (empty($params['type'])){ - $type = 'all'; + if (empty($params['type'])) { + $type = 'all'; } - if ($title = $this->input->getOption('title')){ + if ($title = $this->input->getOption('title')) { $params['title'] = $title; } $appStoreService = ApplicationContext::getContainer()->get(AppStoreService::class); @@ -36,6 +40,12 @@ public function __invoke() $headers = [ 'extensionName', 'description', 'author', 'homePage', 'status', ]; - $this->output->table($headers,$result); + $this->output->table($headers, $result); + } + + protected function configure() + { + $this->addOption('type', 't', InputOption::VALUE_OPTIONAL, 'Type of plugin to query'); + $this->addOption('title', 'title', InputOption::VALUE_OPTIONAL, 'Plugin Title'); } -} \ No newline at end of file +}