mirror of
https://github.com/certd/certd.git
synced 2025-11-25 09:10:11 +08:00
424 lines
10 KiB
TypeScript
424 lines
10 KiB
TypeScript
import {Inject, Provide, Scope, ScopeEnum} from "@midwayjs/core";
|
|
import {BaseService, PageReq} from "@certd/lib-server";
|
|
import {PluginEntity} from "../entity/plugin.js";
|
|
import {InjectEntityModel} from "@midwayjs/typeorm";
|
|
import {Repository} from "typeorm";
|
|
import {isComm} from "@certd/plus-core";
|
|
import {BuiltInPluginService} from "../../pipeline/service/builtin-plugin-service.js";
|
|
import {merge} from "lodash-es";
|
|
import {accessRegistry, notificationRegistry, pluginRegistry} from "@certd/pipeline";
|
|
import {dnsProviderRegistry} from "@certd/plugin-cert";
|
|
import {logger} from "@certd/basic";
|
|
import yaml from "js-yaml";
|
|
import {getDefaultAccessPlugin, getDefaultDeployPlugin, getDefaultDnsPlugin} from "./default-plugin.js";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
|
|
export type PluginImportReq = {
|
|
content: string,
|
|
override?: boolean;
|
|
};
|
|
|
|
@Provide()
|
|
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
|
export class PluginService extends BaseService<PluginEntity> {
|
|
@InjectEntityModel(PluginEntity)
|
|
repository: Repository<PluginEntity>;
|
|
|
|
@Inject()
|
|
builtInPluginService: BuiltInPluginService;
|
|
|
|
//@ts-ignore
|
|
getRepository() {
|
|
return this.repository;
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
async page(pageReq: PageReq<PluginEntity>) {
|
|
|
|
if (pageReq.query.type && pageReq.query.type !== "builtIn") {
|
|
return await super.page(pageReq);
|
|
}
|
|
//仅查询内置插件
|
|
const offset = pageReq.page.offset;
|
|
const limit = pageReq.page.limit;
|
|
|
|
|
|
const builtInList = await this.getBuiltInEntityList();
|
|
|
|
//获取分页数据
|
|
const data = builtInList.slice(offset, offset + limit);
|
|
|
|
return {
|
|
records: data,
|
|
total: builtInList.length,
|
|
offset: offset,
|
|
limit: limit
|
|
};
|
|
}
|
|
|
|
async getEnabledBuildInGroup(isSimple = false) {
|
|
const groups = this.builtInPluginService.getGroups();
|
|
if (isSimple) {
|
|
for (const key in groups) {
|
|
const group = groups[key];
|
|
group.plugins.forEach(item => {
|
|
delete item.input;
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
if (!isComm()) {
|
|
return groups;
|
|
}
|
|
const list = await this.list({
|
|
query: {
|
|
type: "builtIn",
|
|
disabled: true
|
|
}
|
|
});
|
|
const disabledNames = list.map(it => it.name);
|
|
for (const key in groups) {
|
|
const group = groups[key];
|
|
if (!group.plugins) {
|
|
continue;
|
|
}
|
|
group.plugins = group.plugins.filter(it => !disabledNames.includes(it.name));
|
|
}
|
|
return groups;
|
|
}
|
|
|
|
async getEnabledBuiltInList(): Promise<any> {
|
|
const builtInList = this.builtInPluginService.getList();
|
|
if (!isComm()) {
|
|
return builtInList;
|
|
}
|
|
|
|
const list = await this.list({
|
|
query: {
|
|
type: "builtIn",
|
|
disabled: true
|
|
}
|
|
});
|
|
const disabledNames = list.map(it => it.name);
|
|
|
|
return builtInList.filter(it => {
|
|
return !disabledNames.includes(it.name);
|
|
});
|
|
}
|
|
|
|
async getBuiltInEntityList() {
|
|
const builtInList = this.builtInPluginService.getList();
|
|
const list = await this.list({
|
|
query: {
|
|
type: "builtIn"
|
|
}
|
|
});
|
|
|
|
const records: PluginEntity[] = [];
|
|
|
|
for (const item of builtInList) {
|
|
let record = list.find(it => it.name === item.name);
|
|
if (!record) {
|
|
record = new PluginEntity();
|
|
record.disabled = false;
|
|
}
|
|
merge(record, {
|
|
name: item.name,
|
|
title: item.title,
|
|
type: "builtIn",
|
|
icon: item.icon,
|
|
desc: item.desc,
|
|
group: item.group
|
|
});
|
|
records.push(record);
|
|
}
|
|
return records;
|
|
}
|
|
|
|
async setDisabled(opts: { id?: number; name?: string; type: string; disabled: boolean }) {
|
|
const {id, name, type, disabled} = opts;
|
|
if (!type) {
|
|
throw new Error("参数错误: type 不能为空");
|
|
}
|
|
if (id > 0) {
|
|
//update
|
|
await this.repository.update({id}, {disabled});
|
|
return;
|
|
}
|
|
|
|
if (name && type === "builtIn") {
|
|
const pluginEntity = new PluginEntity();
|
|
pluginEntity.name = name;
|
|
pluginEntity.type = type;
|
|
pluginEntity.disabled = disabled;
|
|
await this.repository.save(pluginEntity);
|
|
return;
|
|
}
|
|
throw new Error("参数错误: id 和 name 必须有一个");
|
|
}
|
|
|
|
async getDefineByType(type: string) {
|
|
return this.builtInPluginService.getByType(type);
|
|
}
|
|
|
|
/**
|
|
* 新增
|
|
* @param param 数据
|
|
*/
|
|
async add(param: any) {
|
|
|
|
const old = await this.repository.findOne({
|
|
where: {
|
|
name: param.name,
|
|
author: param.author
|
|
}
|
|
});
|
|
|
|
if (old) {
|
|
throw new Error(`插件${param.author}/${param.name}已存在`);
|
|
}
|
|
|
|
let plugin: any = {};
|
|
if (param.pluginType === "access") {
|
|
plugin = getDefaultAccessPlugin();
|
|
delete param.group;
|
|
} else if (param.pluginType === "deploy") {
|
|
plugin = getDefaultDeployPlugin();
|
|
} else if (param.pluginType === "dnsProvider") {
|
|
plugin = getDefaultDnsPlugin();
|
|
delete param.group;
|
|
} else {
|
|
throw new Error(`插件类型${param.pluginType}不支持`);
|
|
}
|
|
|
|
return await super.add({
|
|
...param,
|
|
...plugin
|
|
});
|
|
}
|
|
|
|
async update(param: any) {
|
|
const old = await this.repository.findOne({
|
|
where: {
|
|
name: param.name,
|
|
author: param.author
|
|
}
|
|
});
|
|
|
|
if (old && old.id !== param.id) {
|
|
throw new Error(`插件${param.author}/${param.name}已存在`);
|
|
}
|
|
|
|
return await super.update(param);
|
|
}
|
|
|
|
async compile(code: string) {
|
|
const ts = await import("typescript");
|
|
return ts.transpileModule(code, {
|
|
compilerOptions: {module: ts.ModuleKind.ESNext}
|
|
}).outputText;
|
|
}
|
|
|
|
|
|
private async getPluginClassFromFile(item: any) {
|
|
const scriptFilePath = item.scriptFilePath;
|
|
const res = await import((`${scriptFilePath}`))
|
|
const classNames = Object.keys(res)
|
|
return res[classNames[0]]
|
|
}
|
|
|
|
async getPluginClassFromDb(pluginName: string) {
|
|
//获取插件类实例对象
|
|
let author = undefined;
|
|
let name = "";
|
|
if (pluginName.includes("/")) {
|
|
const arr = pluginName.split("/");
|
|
author = arr[0];
|
|
name = arr[1];
|
|
} else {
|
|
name = pluginName;
|
|
}
|
|
const info = await this.find({
|
|
where: {
|
|
name: name,
|
|
author: author
|
|
}
|
|
});
|
|
if (info && info.length > 0) {
|
|
const plugin = info[0];
|
|
|
|
try {
|
|
const AsyncFunction = Object.getPrototypeOf(async () => {
|
|
}).constructor;
|
|
// const script = await this.compile(plugin.content);
|
|
const script = plugin.content;
|
|
const getPluginClass = new AsyncFunction(script);
|
|
return await getPluginClass({logger: logger});
|
|
} catch (e) {
|
|
logger.error("编译插件失败:", e);
|
|
throw e;
|
|
}
|
|
|
|
}
|
|
throw new Error(`插件${pluginName}不存在`);
|
|
}
|
|
|
|
/**
|
|
* 从数据库加载插件
|
|
*/
|
|
async registerFromDb() {
|
|
const res = await this.list({
|
|
buildQuery: ((bq) => {
|
|
bq.andWhere("type != :type", {
|
|
type: "builtIn"
|
|
});
|
|
})
|
|
});
|
|
|
|
for (const item of res) {
|
|
await this.registerPlugin(item);
|
|
}
|
|
}
|
|
|
|
async registerFromLocal(localDir: string) {
|
|
//scan path
|
|
const files = fs.readdirSync(localDir);
|
|
let list = []
|
|
for (const file of files) {
|
|
if (!file.endsWith(".yaml")) {
|
|
continue;
|
|
}
|
|
const item = yaml.load(fs.readFileSync(path.join(localDir, file), "utf8"));
|
|
|
|
list.push(item);
|
|
|
|
}
|
|
//排序
|
|
list = list.sort((a, b) => {
|
|
return (a.order??10) - (b.order ??10);
|
|
});
|
|
|
|
for (const item of list) {
|
|
await this.registerPlugin(item);
|
|
}
|
|
}
|
|
|
|
async registerPlugin(plugin: PluginEntity) {
|
|
const metadata = plugin.metadata ? yaml.load(plugin.metadata) : {};
|
|
const item = {
|
|
...plugin,
|
|
...metadata
|
|
};
|
|
delete item.metadata;
|
|
delete item.content;
|
|
if (item.author) {
|
|
item.name = item.author + "/" + item.name;
|
|
}
|
|
let registry = null;
|
|
if (item.pluginType === "access") {
|
|
registry = accessRegistry;
|
|
} else if (item.pluginType === "deploy") {
|
|
registry = pluginRegistry;
|
|
} else if (item.pluginType === "dnsProvider") {
|
|
registry = dnsProviderRegistry;
|
|
} else if (item.pluginType === "notification") {
|
|
registry = notificationRegistry;
|
|
} else {
|
|
logger.warn(`插件${item.name}类型错误:${item.pluginType}`);
|
|
return;
|
|
}
|
|
|
|
registry.register(item.name, {
|
|
define: item,
|
|
target: async () => {
|
|
if (item.type === "builtIn") {
|
|
return await this.getPluginClassFromFile(item);
|
|
} else {
|
|
return await this.getPluginClassFromDb(item.name);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
async exportPlugin(id: number) {
|
|
const info = await this.info(id);
|
|
if (!info) {
|
|
throw new Error("插件不存在");
|
|
}
|
|
const metadata = yaml.load(info.metadata || "");
|
|
const extra = yaml.load(info.extra || "");
|
|
const content = info.content;
|
|
delete info.metadata;
|
|
delete info.extra;
|
|
delete info.content;
|
|
delete info.id;
|
|
delete info.createTime;
|
|
delete info.updateTime;
|
|
const plugin = {
|
|
...info,
|
|
...metadata,
|
|
...extra,
|
|
content
|
|
};
|
|
|
|
return yaml.dump(plugin) as string;
|
|
}
|
|
|
|
async importPlugin(req: PluginImportReq) {
|
|
|
|
const loaded = yaml.load(req.content);
|
|
if (!loaded) {
|
|
throw new Error("插件内容不能为空");
|
|
}
|
|
delete loaded.id
|
|
|
|
const old = await this.repository.findOne({
|
|
where: {
|
|
name: loaded.name,
|
|
author: loaded.author
|
|
}
|
|
});
|
|
|
|
const metadata = {
|
|
input: loaded.input,
|
|
output: loaded.output
|
|
};
|
|
const extra = {
|
|
dependPlugins: loaded.dependPlugins,
|
|
default: loaded.default,
|
|
showRunStrategy: loaded.showRunStrategy
|
|
};
|
|
|
|
const pluginEntity = {
|
|
...loaded,
|
|
metadata: yaml.dump(metadata),
|
|
extra: yaml.dump(extra),
|
|
content: req.content,
|
|
disabled: false
|
|
};
|
|
if (!pluginEntity.pluginType) {
|
|
throw new Error(`插件类型不能为空`);
|
|
}
|
|
|
|
if (old) {
|
|
if (!req.override) {
|
|
throw new Error(`插件${loaded.author}/${loaded.name}已存在`);
|
|
}
|
|
//update
|
|
pluginEntity.id = old.id;
|
|
await this.update(pluginEntity);
|
|
} else {
|
|
//add
|
|
const {id} = await this.add(pluginEntity);
|
|
pluginEntity.id = id;
|
|
}
|
|
return {
|
|
id: pluginEntity.id
|
|
};
|
|
}
|
|
|
|
|
|
}
|