refactor(plugin): 重构插件定义和安装流程

- 更新插件配置格式,增加依赖库和插件类型字段
- 修改插件安装流程,支持安装依赖插件和第三方库
- 优化插件列表过滤逻辑,按类型筛选插件
- 调整 Dockerfile,使用 Node.js22 镜像并更新 pnpm 安装方式
pull/370/head
xiaojunnuo 2025-04-11 23:39:40 +08:00
parent 420b0394a7
commit 3d9620abb0
8 changed files with 176 additions and 85 deletions

View File

@ -44,7 +44,7 @@ jobs:
# cache: 'npm' # cache: 'npm'
# working-directory: ./packages/ui/certd-client # working-directory: ./packages/ui/certd-client
- run: | - run: |
npm install -g pnpm@8.15.7 npm install -g pnpm
pnpm install pnpm install
npm run build npm run build
working-directory: ./packages/ui/certd-client working-directory: ./packages/ui/certd-client

View File

@ -65,6 +65,8 @@ export type PluginDefine = Registrable & {
}; };
needPlus?: boolean; needPlus?: boolean;
showRunStrategy?: boolean; showRunStrategy?: boolean;
pluginType?: string; //类型
type?: string; //来源
}; };
export type ITaskPlugin = { export type ITaskPlugin = {

View File

@ -1,16 +1,16 @@
FROM node:20-alpine AS builder FROM node:22-alpine AS builder
WORKDIR /workspace/ WORKDIR /workspace/
COPY . /workspace/ COPY . /workspace/
# armv7 目前只能用node18 pnpm9不支持node18,所以pnpm只能用8.15.7版本 # armv7 目前只能用node18 pnpm9不支持node18,所以pnpm只能用8.15.7版本
# https://github.com/nodejs/docker-node/issues/1946 # https://github.com/nodejs/docker-node/issues/1946
RUN npm install -g pnpm@8.15.7 RUN npm install -g pnpm
#RUN cd /workspace/certd-client && pnpm install && npm run build #RUN cd /workspace/certd-client && pnpm install && npm run build
RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf
RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker
FROM node:20-alpine FROM node:22-alpine
EXPOSE 7001 EXPOSE 7001
EXPOSE 7002 EXPOSE 7002
RUN apk add --no-cache openssl RUN apk add --no-cache openssl

View File

@ -32,7 +32,7 @@ export default {
}, },
async sleep(ms: number) { async sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
}, },
maxLength(str?: string, length = 100) { maxLength(str?: string, length = 100) {
@ -42,6 +42,9 @@ export default {
return ""; return "";
}, },
transformLink(desc: string = "") { transformLink(desc: string = "") {
return desc.replace(/\[(.*)\]\((.*)\)/g, '<a href="$2" target="_blank">$1</a>'); if (!desc) {
return "";
} }
return desc.replace(/\[(.*)\]\((.*)\)/g, '<a href="$2" target="_blank">$1</a>');
},
}; };

View File

@ -241,7 +241,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
data: [ data: [
{ label: "授权", value: "access" }, { label: "授权", value: "access" },
{ label: "DNS", value: "dnsProvider" }, { label: "DNS", value: "dnsProvider" },
{ label: "部署插件", value: "plugin" }, { label: "部署插件", value: "deploy" },
], ],
}), }),
column: { column: {
@ -279,10 +279,60 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
}, },
"extra.dependLibs": {
title: "第三方依赖",
type: "text",
form: {
helper: "依赖的第三方库,package.dependencies的格式name[:^version]",
component: {
name: "a-select",
mode: "tags",
allowClear: true,
open: false,
},
},
column: {
show: false,
},
},
"extra.dependPlugins": {
title: "插件依赖",
type: "text",
form: {
component: {
name: "a-select",
mode: "tags",
open: false,
allowClear: true,
},
helper: "安装时会先安装依赖的插件,格式:[author/]pluginName[:version]",
},
column: {
show: false,
},
},
"extra.showRunStrategy": {
title: "可修改运行策略",
type: "dict-switch",
dict: dict({
data: [
{ value: false, label: "不可修改" },
{ value: true, label: "可修改" },
],
}),
form: {
value: false,
rules: [{ required: true }],
},
column: {
width: 100,
align: "left",
show: false,
},
},
"extra.default.strategy.runStrategy": { "extra.default.strategy.runStrategy": {
title: "运行策略", title: "运行策略",
type: "dict-select", type: "dict-select",
dict: dict({ dict: dict({
data: [ data: [
{ value: 0, label: "正常运行" }, { value: 0, label: "正常运行" },
@ -293,6 +343,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
value: 1, value: 1,
rules: [{ required: true }], rules: [{ required: true }],
helper: "默认运行策略", helper: "默认运行策略",
show: compute(({ form }) => {
return form.extra.showRunStrategy;
}),
}, },
column: { column: {
width: 100, width: 100,
@ -300,6 +353,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
component: { component: {
color: "auto", color: "auto",
}, },
show: false,
}, },
valueBuilder({ row }) { valueBuilder({ row }) {
if (row.extra) { if (row.extra) {

View File

@ -13,6 +13,10 @@ export class BuiltInPluginService {
if (Plugin?.define?.deprecated) { if (Plugin?.define?.deprecated) {
continue; continue;
} }
//@ts-ignore
if(Plugin.define?.type && Plugin.define?.type !== 'builtin'){
continue;
}
list.push({ ...Plugin.define, key }); list.push({ ...Plugin.define, key });
} }
list = list.sort((a, b) => { list = list.sort((a, b) => {

View File

@ -1,28 +1,36 @@
import yaml from "js-yaml"; import yaml from "js-yaml";
const CertOutputs = [ import { CertApplyPluginNames } from "@certd/plugin-cert";
"CertApply",
"CertApplyLego",
"CertApplyUpload"
];
export function getDefaultAccessPlugin() { export function getDefaultAccessPlugin() {
const metadata = { const metadata = `
username: { input:
title: "用户名", username: #
required: true, title: #
encrypt: false required: true #
}, encrypt: false #
password: { component: #
title: "密码", name: a-input #
required: true, allowClear: true # https://www.antdv.com/components/input#api
password:
title:
required: true
encrypt: true encrypt: true
} component:
}; name: a-input
allowClear: true
const script = `const { BaseAccess } = await import("@certd/pipeline")
`
const script = `
# 使 await import
const { BaseAccess } = await import("@certd/pipeline")
# BaseAccess
return class DemoAccess extends BaseAccess { return class DemoAccess extends BaseAccess {
username; # input
password; username;
password;
} }
`; `;
return { return {
@ -32,21 +40,27 @@ password;
} }
export function getDefaultDeployPlugin() { export function getDefaultDeployPlugin() {
const metadata = {
cert: { let certApplyNames = ''
title: "前置任务证书", for (const name of CertApplyPluginNames) {
component: { certApplyNames += `
name: "output-selector", - ${name}`
from: [...CertOutputs] }
}, const metadata =`
input: #
cert:
title:
helper: #
component:
name: output-selector #
vModel: modelValue #
from:${certApplyNames}
required: true required: true
}, certDomains:
certDomains: { title:
title: "当前证书域名", component:
component: { name: cert-domains-getter
name: "cert-domains-getter" mergeScript: |
},
mergeScript: `
return { return {
component:{ component:{
inputKey: ctx.compute(({form})=>{ inputKey: ctx.compute(({form})=>{
@ -54,60 +68,65 @@ export function getDefaultDeployPlugin() {
}), }),
} }
} }
`,
required: true required: true
}, accessId:
accessId: { title: Access
title: "Access授权", helper: xxxx
helper: "xxxx的授权", component:
component: { name: access-selector #
name: "access-selector", type: aliyun #
type: "aliyun"
},
required: true required: true
}, key1:
key1: { title: 1
title: "输入示例1",
required: false required: false
}, key2:
key2: { title:
title: "可选项", component:
component: { name: a-select
name: "a-select", vMode: value
vMode: "value", options:
options: [ - value: "1"
{ value: "1", label: "选项1" }, label: 1
{ value: "2", label: "选项2" } - value: "2"
] label: 2
},
required: false required: false
} #output: #
}; # outputName:
#
`
const script = ` const script = `
// 要用await来import模块
const { AbstractTaskPlugin } = await import("@certd/pipeline") const { AbstractTaskPlugin } = await import("@certd/pipeline")
// 要返回一个继承AbstractTaskPlugin的class
return class DemoTask extends AbstractTaskPlugin { return class DemoTask extends AbstractTaskPlugin {
// 这里是插件的输入参数对应左边的input配置
cert; cert;
certDomains; certDomains;
accessId; accessId;
key1; key1;
key2; key2;
// 编写执行方法
async execute(){ async execute(){
# accessId
const access = await this.accessService.getById(this.accessId) const access = await this.accessService.getById(this.accessId)
this.logger.info("cert:",this.cert); //必须使用this.logger打印日志
// this.logger.info("cert:",this.cert);
this.logger.info("certDomains:",this.certDomains); this.logger.info("certDomains:",this.certDomains);
this.logger.info("access:",access); this.logger.info("access:",access);
this.logger.info("key1:",this.key1); this.logger.info("key1:",this.key1);
this.logger.info("key2:",this.key2); this.logger.info("key2:",this.key2);
//开始你的部署任务 // 开始你的部署任务
const res = await this.ctx.http.request({url:"xxxxxx"}) // this.ctx里面有一些常用的方法类比如utils、http、logger等
const res = await this.ctx.http.request({url:"https://www.baidu.com"})
if(res.error){ if(res.error){
//抛出异常,终止任务,否则将被判定为执行成功 //抛出异常,终止任务,否则将被判定为执行成功
throw new Error("部署失败:"+res.message) throw new Error("部署失败:"+res.message)
} }
//必须使用this.logger打印日志
this.logger.info("执行成功") this.logger.info("执行成功")
// this.outputName = xxxx //设置输出参数,可以被其他插件选择使用
} }
} }
` `
@ -118,7 +137,14 @@ return class DemoTask extends AbstractTaskPlugin {
} }
export function getDefaultDnsPlugin() { export function getDefaultDnsPlugin() {
const metadata = `accessType: aliyun #授权类型名称` const metadata = `
accessType: aliyun #
#dependPlugins: # 使certdhttplodash-esutils
# @alicloud/openapi-client: ^0.4.12
#dependLibs: #
# aliyun: *
`
const script = ` const script = `
const { AbstractDnsProvider } = await import("@certd/pipeline") const { AbstractDnsProvider } = await import("@certd/pipeline")

View File

@ -86,7 +86,9 @@ export class PluginService extends BaseService<PluginEntity> {
}); });
const disabledNames = list.map(it => it.name); const disabledNames = list.map(it => it.name);
return builtInList.filter(it => !disabledNames.includes(it.name)); return builtInList.filter(it =>{
return !disabledNames.includes(it.name)
});
} }
async getBuiltInEntityList() { async getBuiltInEntityList() {
@ -253,7 +255,7 @@ export class PluginService extends BaseService<PluginEntity> {
let registry = null; let registry = null;
if (item.pluginType === "access") { if (item.pluginType === "access") {
registry = accessRegistry; registry = accessRegistry;
} else if (item.pluginType === "plugin") { } else if (item.pluginType === "deploy") {
registry = pluginRegistry; registry = pluginRegistry;
} else if (item.pluginType === "dnsProvider") { } else if (item.pluginType === "dnsProvider") {
registry = dnsProviderRegistry; registry = dnsProviderRegistry;