mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-24 00:55:35 +08:00
refactor plugin command and add plugin:install command
This commit is contained in:
@@ -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": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,7 +26,7 @@ class LoadConfiguration
|
|||||||
|
|
||||||
private function getConfigDir(array $config): string
|
private function getConfigDir(array $config): string
|
||||||
{
|
{
|
||||||
$config_dir = $config['config-dir'];
|
$config_dir = $config['config-dir'] ?? null;
|
||||||
// 默认配置文件目录
|
// 默认配置文件目录
|
||||||
$find_dir = [
|
$find_dir = [
|
||||||
WORKING_DIR . '/config',
|
WORKING_DIR . '/config',
|
||||||
|
|||||||
18
src/ZM/Bootstrap/LoadPlugins.php
Normal file
18
src/ZM/Bootstrap/LoadPlugins.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ZM\Bootstrap;
|
||||||
|
|
||||||
|
use ZM\Plugin\PluginManager;
|
||||||
|
|
||||||
|
class LoadPlugins
|
||||||
|
{
|
||||||
|
public function bootstrap(array $config): void
|
||||||
|
{
|
||||||
|
// 先遍历下插件目录下是否有这个插件,没有这个插件则不能打包
|
||||||
|
$plugin_dir = config('global.plugin.load_dir', SOURCE_ROOT_DIR . '/plugins');
|
||||||
|
// 模拟加载一遍插件
|
||||||
|
PluginManager::addPluginsFromDir($plugin_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/ZM/Command/Plugin/PluginCommand.php
Normal file
25
src/ZM/Command/Plugin/PluginCommand.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ZM\Command\Plugin;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use ZM\Bootstrap;
|
||||||
|
use ZM\Command\Command;
|
||||||
|
|
||||||
|
abstract class PluginCommand extends Command
|
||||||
|
{
|
||||||
|
protected array $bootstrappers = [
|
||||||
|
BootStrap\RegisterLogger::class,
|
||||||
|
Bootstrap\SetInternalTimezone::class,
|
||||||
|
Bootstrap\LoadConfiguration::class,
|
||||||
|
Bootstrap\LoadPlugins::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
return parent::execute($input, $output);
|
||||||
|
}
|
||||||
|
}
|
||||||
108
src/ZM/Command/Plugin/PluginInstallCommand.php
Normal file
108
src/ZM/Command/Plugin/PluginInstallCommand.php
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ZM\Command\Plugin;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use ZM\Plugin\PluginManager;
|
||||||
|
use ZM\Store\FileSystem;
|
||||||
|
use ZM\Utils\ZMRequest;
|
||||||
|
|
||||||
|
#[AsCommand(name: 'plugin:install', description: '从 GitHub 或其他 Git 源码托管站安装插件')]
|
||||||
|
class PluginInstallCommand extends PluginCommand
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,20 +10,12 @@ use Symfony\Component\Console\Input\InputArgument;
|
|||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||||
use Symfony\Component\Console\Question\Question;
|
use Symfony\Component\Console\Question\Question;
|
||||||
use ZM\Bootstrap;
|
|
||||||
use ZM\Command\Command;
|
|
||||||
use ZM\Store\FileSystem;
|
use ZM\Store\FileSystem;
|
||||||
use ZM\Utils\CodeGenerator\PluginGenerator;
|
use ZM\Utils\CodeGenerator\PluginGenerator;
|
||||||
|
|
||||||
#[AsCommand(name: 'plugin:make', description: '创建一个新的插件')]
|
#[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()
|
protected function configure()
|
||||||
{
|
{
|
||||||
$this->addArgument('name', InputArgument::OPTIONAL, '插件名称', null);
|
$this->addArgument('name', InputArgument::OPTIONAL, '插件名称', null);
|
||||||
|
|||||||
@@ -6,23 +6,19 @@ namespace ZM\Command\Plugin;
|
|||||||
|
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use ZM\Bootstrap;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use ZM\Command\Command;
|
|
||||||
use ZM\Exception\PluginException;
|
use ZM\Exception\PluginException;
|
||||||
use ZM\Plugin\PluginManager;
|
use ZM\Plugin\PluginManager;
|
||||||
|
|
||||||
#[AsCommand(name: 'plugin:pack', description: '打包插件到 Phar 格式')]
|
#[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()
|
protected function configure()
|
||||||
{
|
{
|
||||||
$this->addArgument('name', InputArgument::REQUIRED, '要打包的插件名称');
|
$this->addArgument('name', InputArgument::REQUIRED, '要打包的插件名称');
|
||||||
|
|
||||||
|
// 下面是辅助用的,和 server:start 一样
|
||||||
|
$this->addOption('config-dir', null, InputOption::VALUE_REQUIRED, '指定其他配置文件目录');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ class PluginManager
|
|||||||
*/
|
*/
|
||||||
public static function addPlugin(PluginMeta $meta): void
|
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 对象
|
// 首先看看有没有 entity,如果还没有 entity,且 entry_file 有东西,那么就从 entry_file 获取 ZMPlugin 对象
|
||||||
if ($meta->getEntity() === null) {
|
if ($meta->getEntity() === null) {
|
||||||
if (($entry_file = $meta->getEntryFile()) !== null) {
|
if (($entry_file = $meta->getEntryFile()) !== null) {
|
||||||
@@ -181,7 +181,7 @@ class PluginManager
|
|||||||
}
|
}
|
||||||
// 检查同名插件,如果有同名插件,则抛出异常
|
// 检查同名插件,如果有同名插件,则抛出异常
|
||||||
if (isset(self::$plugins[$meta->getName()])) {
|
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;
|
self::$plugins[$meta->getName()] = $meta;
|
||||||
}
|
}
|
||||||
@@ -261,11 +261,6 @@ class PluginManager
|
|||||||
*/
|
*/
|
||||||
public static function packPlugin(string $name): string
|
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) {
|
if (!isset(self::$plugins[$name]) || self::$plugins[$name]->getPluginType() !== ZM_PLUGIN_TYPE_SOURCE) {
|
||||||
throw new PluginException("没有找到名字为 {$name} 的插件(要打包的插件必须是源码模式)。");
|
throw new PluginException("没有找到名字为 {$name} 的插件(要打包的插件必须是源码模式)。");
|
||||||
@@ -278,4 +273,14 @@ class PluginManager
|
|||||||
// 插件加载方式判断
|
// 插件加载方式判断
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查插件是否被加载
|
||||||
|
*
|
||||||
|
* @param string $name 插件名称
|
||||||
|
*/
|
||||||
|
public static function isPluginExists(string $name): bool
|
||||||
|
{
|
||||||
|
return isset(self::$plugins[$name]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user