update plugin install and load strategy

This commit is contained in:
crazywhalecc
2023-02-28 23:07:09 +08:00
committed by Jerry
parent 9c599ff54b
commit 64c22328a1
10 changed files with 336 additions and 134 deletions

View File

@@ -125,9 +125,9 @@ abstract class PluginCommand extends Command
{
return match ($type) {
ZM_PLUGIN_TYPE_NATIVE => '内部',
ZM_PLUGIN_TYPE_PHAR => 'Phar',
ZM_PLUGIN_TYPE_SOURCE => '源码',
ZM_PLUGIN_TYPE_COMPOSER => 'Composer 外部加载',
ZM_PLUGIN_TYPE_PHAR => '<comment>Phar</comment>',
ZM_PLUGIN_TYPE_SOURCE => '<fg=gray>源码</>',
ZM_PLUGIN_TYPE_COMPOSER => '<info>Composer</info>',
default => '未知模式'
};
}

View File

@@ -7,10 +7,9 @@ namespace ZM\Command\Plugin;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use ZM\Exception\FileSystemException;
use ZM\Plugin\PluginManager;
use ZM\Plugin\Strategy\ComposerStrategy;
use ZM\Plugin\Strategy\GitStrategy;
use ZM\Store\FileSystem;
use ZM\Utils\ZMRequest;
#[AsCommand(name: 'plugin:install', description: '从 GitHub 或其他 Git 源码托管站安装插件')]
class PluginInstallCommand extends PluginCommand
@@ -18,6 +17,7 @@ class PluginInstallCommand extends PluginCommand
protected function configure()
{
$this->addArgument('address', InputArgument::REQUIRED, '插件地址');
$this->addOption('github-token', null, InputOption::VALUE_REQUIRED, '提供的 GitHub Token');
// 下面是辅助用的,和 server:start 一样
$this->addOption('config-dir', null, InputOption::VALUE_REQUIRED, '指定其他配置文件目录');
@@ -29,87 +29,36 @@ class PluginInstallCommand extends PluginCommand
protected function handle(): int
{
$addr = $this->input->getArgument('address');
$name = ob_uuidgen();
$this->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;
}
// 先检查传入的参数是什么类型。如果是 a/b 类型则为 composer 包
if (count(explode('/', $addr)) === 2) {
$st = new ComposerStrategy($addr, $this->plugin_dir, logger: $this);
} elseif ((parse_url($addr)['scheme'] ?? null) !== null) {
$st = new GitStrategy($addr, $this->plugin_dir, logger: $this);
} else {
$this->error('无法检测输入要安装插件的链接或名字,请检查后再试!');
return static::FAILURE;
}
$this->info('正在从 ' . $addr . ' 克隆插件仓库');
if ($token = $this->input->getOption('github-token')) {
$option = ['github-token' => $token];
} else {
$option = [];
}
// 然后调用安装并看是否成功
try {
FileSystem::createDir($this->plugin_dir);
} catch (FileSystemException $exception) {
$this->error("无法创建插件目录 {$this->plugin_dir}{$exception->getMessage()}");
return static::FAILURE;
}
passthru('cd ' . escapeshellarg($this->plugin_dir) . ' && git clone --depth=1 ' . escapeshellarg($addr) . ' ' . $name, $code);
if ($code !== 0) {
$this->error('无法从指定 Git 地址拉取项目,请检查地址名是否正确');
return static::FAILURE;
}
if (!file_exists($this->plugin_dir . '/' . $name . '/zmplugin.json')) {
$this->error('项目不存在 zmplugin.json 插件元信息,无法安装,请手动删除目录 ' . $name);
// TODO: 使用 rmdir 和 unlink 删除 git 目录
return static::FAILURE;
}
$this->output->writeln('正在检查元信息完整性');
$getname = json_decode(file_get_contents($this->plugin_dir . '/' . $name . '/zmplugin.json'), true)['name'] ?? null;
if ($getname === null) {
$this->error('无法获取元信息 zmplugin.json');
return static::FAILURE;
}
$code = rename($this->plugin_dir . '/' . $name, $this->plugin_dir . '/' . $getname);
if ($code === false) {
$this->error('无法重命名文件夹 ' . $name);
return static::FAILURE;
}
if (file_exists($this->plugin_dir . '/' . $getname . '/composer.json')) {
$this->info('插件存在 composer.json正在安装 composer 相关依赖(需要系统环境变量中包含 composer 路径)');
$cwd = getcwd();
chdir($this->plugin_dir . '/' . $getname);
// 使用内建 Composer
if (file_exists(WORKING_DIR . '/runtime/composer.phar')) {
$this->info('使用内建 Composer');
passthru(PHP_BINARY . ' ' . 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 是否可以正常运行');
if (!$st->install($option)) {
$this->error('插件安装失败,' . $st->getError());
return static::FAILURE;
}
} catch (\Throwable $e) {
$this->error('niu: ' . $e->getMessage());
$this->error($e->getTraceAsString());
return static::FAILURE;
}
$this->info('插件 ' . $getname . ' 安装成功!');
$this->info('插件 ' . $st->getInstalledName() . ' 安装成功!');
return static::SUCCESS;
}
}

View File

@@ -11,13 +11,30 @@ use ZM\Plugin\PluginManager;
#[AsCommand(name: 'plugin:list', description: '显示插件列表')]
class PluginListCommand extends PluginCommand
{
protected function configure()
{
$this->addOption('name-list', 'N', null, '只输出插件列表的名字');
}
protected function handle(): int
{
$all = PluginManager::getPlugins();
if ($all === []) {
$this->info('当前未安装任何插件');
return static::SUCCESS;
}
if ($this->input->getOption('name-list')) {
$this->info('插件列表: ');
foreach ($all as $k => $v) {
$this->write($k);
}
return static::SUCCESS;
}
$table = new Table($this->output);
$table->setHeaders(['名称', '版本', '类型']);
$table->setColumnMaxWidth(2, 27);
$table->setHeaders(['名称', '版本', '简介', '类型']);
foreach ($all as $k => $v) {
$table->addRow([$k, $v->getVersion(), $this->getTypeDisplayName($v->getPluginType())]);
$table->addRow([$k, $v->getVersion(), $v->getDescription(), $this->getTypeDisplayName($v->getPluginType())]);
}
$table->setStyle('box');
$table->render();