From 2e048515fbc27df47013dfa8ace2b10dd081fb4a Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 18 Jan 2023 00:56:12 +0800 Subject: [PATCH] refactor plugin command and add plugin:install command --- config/console_color.json | 29 ----- src/ZM/Bootstrap/LoadConfiguration.php | 2 +- src/ZM/Bootstrap/LoadPlugins.php | 18 +++ src/ZM/Command/Plugin/PluginCommand.php | 25 ++++ .../Command/Plugin/PluginInstallCommand.php | 108 ++++++++++++++++++ src/ZM/Command/Plugin/PluginMakeCommand.php | 10 +- src/ZM/Command/Plugin/PluginPackCommand.php | 14 +-- src/ZM/Plugin/PluginManager.php | 19 +-- 8 files changed, 170 insertions(+), 55 deletions(-) delete mode 100644 config/console_color.json create mode 100644 src/ZM/Bootstrap/LoadPlugins.php create mode 100644 src/ZM/Command/Plugin/PluginCommand.php create mode 100644 src/ZM/Command/Plugin/PluginInstallCommand.php diff --git a/config/console_color.json b/config/console_color.json deleted file mode 100644 index ffb81891..00000000 --- a/config/console_color.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "default": { - "success": "green", - "info": "lightblue", - "warning": "yellow", - "error": "red", - "verbose": "blue", - "debug": "gray", - "trace": "gray" - }, - "white-term": { - "success": "green", - "info": "", - "warning": "yellow", - "error": "red", - "verbose": "blue", - "debug": "gray", - "trace": "gray" - }, - "no-color": { - "success": "", - "info": "", - "warning": "", - "error": "", - "verbose": "", - "debug": "", - "trace": "" - } -} diff --git a/src/ZM/Bootstrap/LoadConfiguration.php b/src/ZM/Bootstrap/LoadConfiguration.php index 44b9ea57..2966daa5 100644 --- a/src/ZM/Bootstrap/LoadConfiguration.php +++ b/src/ZM/Bootstrap/LoadConfiguration.php @@ -26,7 +26,7 @@ class LoadConfiguration private function getConfigDir(array $config): string { - $config_dir = $config['config-dir']; + $config_dir = $config['config-dir'] ?? null; // 默认配置文件目录 $find_dir = [ WORKING_DIR . '/config', diff --git a/src/ZM/Bootstrap/LoadPlugins.php b/src/ZM/Bootstrap/LoadPlugins.php new file mode 100644 index 00000000..b7de2ef1 --- /dev/null +++ b/src/ZM/Bootstrap/LoadPlugins.php @@ -0,0 +1,18 @@ +addArgument('address', InputArgument::REQUIRED, '插件地址'); + + // 下面是辅助用的,和 server:start 一样 + $this->addOption('config-dir', null, InputOption::VALUE_REQUIRED, '指定其他配置文件目录'); + } + + /** + * {@inheritDoc} + */ + protected function handle(): int + { + $addr = $this->input->getArgument('address'); + $name = ob_uuidgen(); + $plugin_dir = FileSystem::isRelativePath(config('global.plugin.load_dir', 'plugins')) ? (WORKING_DIR . '/' . config('global.plugin.load_dir', 'plugins')) : config('global.plugin.load_dir', 'plugins'); + // 先通过 GitHub API 获取看看存不存在 zmplugin.json + // 解析 git https 路径中的仓库所有者和仓库名 + $git_url = parse_url($addr); + if ($git_url['host'] === 'github.com') { + $path = explode('/', $git_url['path']); + $owner = $path[1]; + $repo = $path[2]; + if (str_ends_with($repo, '.git')) { + $repo = substr($repo, 0, -4); + } + $api = ZMRequest::get('https://api.github.com/repos/' . $owner . '/' . $repo . '/contents/zmplugin.json', ['User-Agent' => 'ZMFramework']); + if ($api === false) { + $this->error('GitHub API 请求失败'); + return static::FAILURE; + } + $api = json_decode($api, true); + if (isset($api['message'])) { + $this->error('该项目中不存在 zmplugin.json 元信息!'); + return static::FAILURE; + } + $contents = implode('', array_map(fn ($x) => base64_decode($x), explode("\n", $api['content']))); + $json = json_decode($contents, true); + if (!isset($json['name'])) { + $this->error('插件元信息内没有名字!'); + return static::FAILURE; + } + $plugin_name = $json['name']; + if (PluginManager::isPluginExists($plugin_name)) { + $this->error('插件 ' . $plugin_name . ' 已存在,无法再次安装!'); + return static::FAILURE; + } + } + $this->info('正在从 ' . $addr . ' 克隆插件仓库'); + passthru('cd ' . escapeshellarg($plugin_dir) . ' && git clone --depth=1 ' . escapeshellarg($addr) . ' ' . $name, $code); + if ($code !== 0) { + $this->error('无法从指定 Git 地址拉取项目,请检查地址名是否正确'); + return static::FAILURE; + } + if (!file_exists($plugin_dir . '/' . $name . '/zmplugin.json')) { + $this->error('项目不存在 zmplugin.json 插件元信息,无法安装'); + // TODO: 使用 rmdir 和 unlink 删除 git 目录 + return static::FAILURE; + } + $this->output->writeln('正在检查元信息完整性'); + $getname = json_decode(file_get_contents($plugin_dir . '/' . $name . '/zmplugin.json'), true)['name'] ?? null; + if ($getname === null) { + $this->error('无法获取元信息 zmplugin.json'); + return static::FAILURE; + } + $code = rename($plugin_dir . '/' . $name, $plugin_dir . '/' . $getname); + if ($code === false) { + $this->error('无法重命名文件夹 ' . $name); + return static::FAILURE; + } + if (file_exists($plugin_dir . '/' . $getname . '/composer.json')) { + $this->info('插件存在 composer.json,正在安装 composer 相关依赖(需要系统环境变量中包含 composer 路径)'); + $cwd = getcwd(); + chdir($plugin_dir . '/' . $getname); + // 使用内建 Composer + if (file_exists(WORKING_DIR . '/runtime/composer.phar')) { + $this->info('使用内建 Composer'); + passthru('php ' . escapeshellarg(WORKING_DIR . '/runtime/composer.phar') . ' install --no-dev', $code); + } else { + $this->info('使用系统 Composer'); + passthru('composer install --no-dev', $code); + } + chdir($cwd); + if ($code != 0) { + $this->error('无法安装 Composer 依赖,请检查 Composer 是否可以正常运行'); + return static::FAILURE; + } + } + $this->info('插件 ' . $getname . ' 安装成功!'); + return static::SUCCESS; + } +} diff --git a/src/ZM/Command/Plugin/PluginMakeCommand.php b/src/ZM/Command/Plugin/PluginMakeCommand.php index d55dbfc3..d70f9253 100644 --- a/src/ZM/Command/Plugin/PluginMakeCommand.php +++ b/src/ZM/Command/Plugin/PluginMakeCommand.php @@ -10,20 +10,12 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\Question; -use ZM\Bootstrap; -use ZM\Command\Command; use ZM\Store\FileSystem; use ZM\Utils\CodeGenerator\PluginGenerator; #[AsCommand(name: 'plugin:make', description: '创建一个新的插件')] -class PluginMakeCommand extends Command +class PluginMakeCommand extends PluginCommand { - protected array $bootstrappers = [ - BootStrap\RegisterLogger::class, - Bootstrap\SetInternalTimezone::class, - Bootstrap\LoadConfiguration::class, - ]; - protected function configure() { $this->addArgument('name', InputArgument::OPTIONAL, '插件名称', null); diff --git a/src/ZM/Command/Plugin/PluginPackCommand.php b/src/ZM/Command/Plugin/PluginPackCommand.php index 53a87a85..f5e342c9 100644 --- a/src/ZM/Command/Plugin/PluginPackCommand.php +++ b/src/ZM/Command/Plugin/PluginPackCommand.php @@ -6,23 +6,19 @@ namespace ZM\Command\Plugin; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; -use ZM\Bootstrap; -use ZM\Command\Command; +use Symfony\Component\Console\Input\InputOption; use ZM\Exception\PluginException; use ZM\Plugin\PluginManager; #[AsCommand(name: 'plugin:pack', description: '打包插件到 Phar 格式')] -class PluginPackCommand extends Command +class PluginPackCommand extends PluginCommand { - protected array $bootstrappers = [ - BootStrap\RegisterLogger::class, - Bootstrap\SetInternalTimezone::class, - Bootstrap\LoadConfiguration::class, - ]; - protected function configure() { $this->addArgument('name', InputArgument::REQUIRED, '要打包的插件名称'); + + // 下面是辅助用的,和 server:start 一样 + $this->addOption('config-dir', null, InputOption::VALUE_REQUIRED, '指定其他配置文件目录'); } /** diff --git a/src/ZM/Plugin/PluginManager.php b/src/ZM/Plugin/PluginManager.php index 6713dde6..24df5972 100644 --- a/src/ZM/Plugin/PluginManager.php +++ b/src/ZM/Plugin/PluginManager.php @@ -160,7 +160,7 @@ class PluginManager */ public static function addPlugin(PluginMeta $meta): void { - logger()->debug('Adding plugin: ' . $meta->getName()); + logger()->debug('Adding plugin: ' . $meta->getName() . '(type:' . $meta->getPluginType() . ')'); // 首先看看有没有 entity,如果还没有 entity,且 entry_file 有东西,那么就从 entry_file 获取 ZMPlugin 对象 if ($meta->getEntity() === null) { if (($entry_file = $meta->getEntryFile()) !== null) { @@ -181,7 +181,7 @@ class PluginManager } // 检查同名插件,如果有同名插件,则抛出异常 if (isset(self::$plugins[$meta->getName()])) { - throw new PluginException('插件 ' . $meta->getName() . ' 已经存在,无法加载同名插件或重复加载!'); + throw new PluginException('插件 ' . $meta->getName() . ' 已经存在(类型为' . self::$plugins[$meta->getName()]->getPluginType() . '),无法加载同名插件或重复加载!'); } self::$plugins[$meta->getName()] = $meta; } @@ -261,11 +261,6 @@ class PluginManager */ public static function packPlugin(string $name): string { - // 先遍历下插件目录下是否有这个插件,没有这个插件则不能打包 - $plugin_dir = config('global.plugin.load_dir', SOURCE_ROOT_DIR . '/plugins'); - // 模拟加载一遍插件 - self::addPluginsFromDir($plugin_dir); - // 必须是源码模式才行 if (!isset(self::$plugins[$name]) || self::$plugins[$name]->getPluginType() !== ZM_PLUGIN_TYPE_SOURCE) { throw new PluginException("没有找到名字为 {$name} 的插件(要打包的插件必须是源码模式)。"); @@ -278,4 +273,14 @@ class PluginManager // 插件加载方式判断 return ''; } + + /** + * 检查插件是否被加载 + * + * @param string $name 插件名称 + */ + public static function isPluginExists(string $name): bool + { + return isset(self::$plugins[$name]); + } }