From 167b303faeca02cc11cf97e4be2a3df914852167 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Wed, 15 Jul 2026 01:09:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E5=88=B0cos=E6=8A=A5runtimeDepsService=E6=9C=AA=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- C | 1 + packages/core/pipeline/src/access/api.ts | 11 +- .../core/pipeline/src/notification/api.ts | 11 +- packages/core/pipeline/src/plugin/api.ts | 13 +- .../service}/npm-registry-resolver.test.ts | 32 +- .../src/service}/runtime-deps-service.test.ts | 332 ++----------- .../src/service}/runtime-deps-service.ts | 466 ++++++++++-------- packages/core/pipeline/src/service/runtime.ts | 30 +- .../libs/lib-server/src/user/addon/api/api.ts | 34 +- .../plugin-lib/src/cert/dns-provider/base.ts | 11 +- packages/ui/certd-client/package.json | 1 - packages/ui/certd-server/.eslintrc | 3 +- packages/ui/certd-server/package.json | 2 + .../sys/settings/sys-settings-controller.ts | 14 +- .../src/modules/auto/auto-a-register.ts | 8 +- .../src/modules/auto/auto-load-plugins.ts | 27 +- .../src/modules/basic/service/code-service.ts | 14 +- .../certd-server/src/modules/basic/sms/api.ts | 6 +- .../src/modules/basic/sms/tencent-sms.ts | 23 +- .../mine/service/two-factor-service.ts | 17 +- .../service/getter/task-service-getter.ts | 12 +- .../pipeline/service/pipeline-service.ts | 6 +- .../modules/plugin/service/plugin-service.ts | 23 +- .../runtime-deps/npm-registry-resolver.ts | 80 --- .../dns-provider/aliyun-dns-provider.ts | 5 +- .../plugin/deploy-to-ack/index.ts | 5 +- .../plugin/deploy-to-alb/index.ts | 9 +- .../plugin/deploy-to-cdn/index.ts | 7 +- .../plugin/deploy-to-dcdn/index.ts | 5 +- .../plugin/deploy-to-fc/index.ts | 10 +- .../plugin/deploy-to-nlb/index.ts | 9 +- .../plugin/deploy-to-oss/index.ts | 2 +- .../plugin/deploy-to-slb/index.ts | 7 +- .../deploy-to-waf/deploy-to-waf-cloud.ts | 5 +- .../deploy-to-waf/deploy-to-waf-cname.ts | 7 +- .../plugin-lib/aliyun/access/alioss-access.ts | 2 +- .../plugin-lib/aliyun/access/aliyun-access.ts | 7 +- .../plugin-lib/aliyun/lib/aliyun-client-v2.ts | 18 +- .../plugin-lib/aliyun/lib/base-client.ts | 15 +- .../plugin-lib/aliyun/lib/ssl-client.ts | 9 +- .../plugin-lib/oss/impls/tencentcos.ts | 5 +- .../src/plugins/plugin-lib/tencent/access.ts | 2 +- .../plugin-lib/tencent/lib/cos-client.ts | 15 +- .../NginxProxyManagerDeploy.yaml | 348 ------------- .../nginxProxyManager.yaml | 347 ------------- .../plugin/deploy-to-cos/index.ts | 2 +- .../plugin/deploy-to-live/index.ts | 2 +- .../plugin/upload-to-tencent/index.ts | 2 +- .../plugins/plugin-volcengine/cdn-client.ts | 7 +- .../plugins/plugin-volcengine/dns-client.ts | 6 +- .../plugins/plugin-deploy-to-vod.ts | 2 +- .../plugins/plugin-volcengine/ve-client.ts | 12 +- .../volcengine-dns-provider.ts | 1 - 53 files changed, 517 insertions(+), 1533 deletions(-) create mode 100644 C rename packages/{ui/certd-server/src/modules/runtime-deps => core/pipeline/src/service}/npm-registry-resolver.test.ts (58%) rename packages/{ui/certd-server/src/modules/runtime-deps => core/pipeline/src/service}/runtime-deps-service.test.ts (52%) rename packages/{ui/certd-server/src/modules/runtime-deps => core/pipeline/src/service}/runtime-deps-service.ts (72%) delete mode 100644 packages/ui/certd-server/src/modules/runtime-deps/npm-registry-resolver.ts delete mode 100644 packages/ui/certd-server/src/plugins/plugin-nginx-proxy-manager/NginxProxyManagerDeploy.yaml delete mode 100644 packages/ui/certd-server/src/plugins/plugin-nginx-proxy-manager/nginxProxyManager.yaml diff --git a/C b/C new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/C @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/pipeline/src/access/api.ts b/packages/core/pipeline/src/access/api.ts index d72c71cc5..92ee6c371 100644 --- a/packages/core/pipeline/src/access/api.ts +++ b/packages/core/pipeline/src/access/api.ts @@ -3,7 +3,7 @@ import { FormItemProps } from "../dt/index.js"; import { HttpClient, ILogger, utils } from "@certd/basic"; import * as _ from "lodash-es"; import { PluginRequestHandleReq } from "../plugin/index.js"; -import { IRuntimeDepsService, IServiceGetter } from "../service/index.js"; +import { IServiceGetter, getRuntimeDepsService } from "../service/index.js"; // export type AccessRequestHandleReqInput = { // id?: number; @@ -48,20 +48,13 @@ export type AccessContext = { export abstract class BaseAccess implements IAccess { ctx!: AccessContext; - runtimeDepsService?: IRuntimeDepsService; async importRuntime(specifier: string) { - if (!this.runtimeDepsService) { - throw new Error("runtimeDepsService 未初始化"); - } - return await this.runtimeDepsService.importRuntime(specifier, this.ctx.logger); + return await getRuntimeDepsService().importRuntime(specifier, this.ctx.logger); } async setCtx(ctx: AccessContext) { this.ctx = ctx; - if (!this.runtimeDepsService && this.ctx.serviceGetter) { - this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService"); - } } async onRequest(req: AccessRequestHandleReq) { diff --git a/packages/core/pipeline/src/notification/api.ts b/packages/core/pipeline/src/notification/api.ts index c083fe42c..aa9481178 100644 --- a/packages/core/pipeline/src/notification/api.ts +++ b/packages/core/pipeline/src/notification/api.ts @@ -3,7 +3,7 @@ import { Registrable } from "../registry/index.js"; import { FormItemProps, HistoryResult, Pipeline } from "../dt/index.js"; import { HttpClient, ILogger, utils } from "@certd/basic"; import * as _ from "lodash-es"; -import { IEmailService, IRuntimeDepsService, IServiceGetter } from "../service/index.js"; +import { IEmailService, IServiceGetter, getRuntimeDepsService } from "../service/index.js"; export type NotificationBody = { userId?: number; @@ -89,13 +89,9 @@ export abstract class BaseNotification implements INotification { ctx!: NotificationContext; http!: HttpClient; logger!: ILogger; - runtimeDepsService?: IRuntimeDepsService; async importRuntime(specifier: string) { - if (!this.runtimeDepsService) { - return await import(specifier); - } - return await this.runtimeDepsService.importRuntime(specifier, this.logger); + return await getRuntimeDepsService().importRuntime(specifier, this.logger); } async doSend(body: NotificationBody) { @@ -109,9 +105,6 @@ export abstract class BaseNotification implements INotification { this.ctx = ctx; this.http = ctx.http; this.logger = ctx.logger; - if (!this.runtimeDepsService && this.ctx.serviceGetter) { - this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService"); - } } setDefine = (define: NotificationDefine) => { this.define = define; diff --git a/packages/core/pipeline/src/plugin/api.ts b/packages/core/pipeline/src/plugin/api.ts index c01ae3795..544ad2c01 100644 --- a/packages/core/pipeline/src/plugin/api.ts +++ b/packages/core/pipeline/src/plugin/api.ts @@ -10,7 +10,7 @@ import { INotificationService } from "../notification/index.js"; import { Registrable } from "../registry/index.js"; import { IPluginConfigService } from "../service/config.js"; import { TaskEmitter } from "../service/emit.js"; -import { ICnameProxyService, IEmailService, IRuntimeDepsService, IServiceGetter, IUrlService } from "../service/index.js"; +import { ICnameProxyService, IEmailService, IServiceGetter, IUrlService, getRuntimeDepsService } from "../service/index.js"; export type PluginRequestHandleReq = { typeName: string; @@ -76,7 +76,7 @@ export type ITaskPlugin = { execute(): Promise; onRequest(req: PluginRequestHandleReq): Promise; setCtx(ctx: TaskInstanceContext): Promise; - importRuntime?(specifier: string): Promise; + importRuntime(specifier: string): Promise; [key: string]: any; }; @@ -150,13 +150,9 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin { logger!: ILogger; http!: HttpClient; accessService!: IAccessService; - runtimeDepsService!: IRuntimeDepsService; async importRuntime(specifier: string) { - if (!this.runtimeDepsService) { - throw new Error("runtimeDepsService 未初始化"); - } - return await this.runtimeDepsService.importRuntime(specifier, this.logger); + return await getRuntimeDepsService().importRuntime(specifier, this.logger); } clearLastStatus() { @@ -178,9 +174,6 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin { this.logger = ctx.logger; this.accessService = ctx.accessService; this.http = ctx.http; - if (!this.runtimeDepsService && this.ctx.serviceGetter) { - this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService"); - } // 将证书加入secret // @ts-ignore if (this.cert && this.cert.crt && this.cert.key) { diff --git a/packages/ui/certd-server/src/modules/runtime-deps/npm-registry-resolver.test.ts b/packages/core/pipeline/src/service/npm-registry-resolver.test.ts similarity index 58% rename from packages/ui/certd-server/src/modules/runtime-deps/npm-registry-resolver.test.ts rename to packages/core/pipeline/src/service/npm-registry-resolver.test.ts index 47c9b88b1..988db70f8 100644 --- a/packages/ui/certd-server/src/modules/runtime-deps/npm-registry-resolver.test.ts +++ b/packages/core/pipeline/src/service/npm-registry-resolver.test.ts @@ -1,41 +1,31 @@ -import assert from "assert"; -import { NpmRegistryResolver } from "./npm-registry-resolver.js"; +import assert from "assert"; +import { NpmRegistryResolver } from "./runtime-deps-service.js"; describe("NpmRegistryResolver", () => { it("chooses the fastest successful registry in auto mode", async () => { - const resolver = new NpmRegistryResolver(); - resolver.config = { + const resolver = new NpmRegistryResolver({ mode: "auto", - fixedUrl: "", candidates: ["https://slow.example.com", "https://fast.example.com"], probeTimeoutMs: 100, cacheTtlMs: 1000, - }; - resolver.probe = async registryUrl => { - return { - registryUrl, - ok: true, - elapsedMs: registryUrl.includes("fast") ? 10 : 50, - }; - }; - + }); + resolver.probe = async registryUrl => ({ + registryUrl, + ok: true, + elapsedMs: registryUrl.includes("fast") ? 10 : 50, + }); const result = await resolver.resolve(); - assert.equal(result, "https://fast.example.com"); }); it("uses fixed registry without probing", async () => { - const resolver = new NpmRegistryResolver(); - resolver.config = { + const resolver = new NpmRegistryResolver({ mode: "fixed", fixedUrl: "https://registry.example.com", - candidates: [], probeTimeoutMs: 100, cacheTtlMs: 1000, - }; - + }); const result = await resolver.resolve(); - assert.equal(result, "https://registry.example.com"); }); }); diff --git a/packages/ui/certd-server/src/modules/runtime-deps/runtime-deps-service.test.ts b/packages/core/pipeline/src/service/runtime-deps-service.test.ts similarity index 52% rename from packages/ui/certd-server/src/modules/runtime-deps/runtime-deps-service.test.ts rename to packages/core/pipeline/src/service/runtime-deps-service.test.ts index 02cbd6f73..4b3aebcaa 100644 --- a/packages/ui/certd-server/src/modules/runtime-deps/runtime-deps-service.test.ts +++ b/packages/core/pipeline/src/service/runtime-deps-service.test.ts @@ -1,10 +1,10 @@ -import assert from "assert"; +import assert from "assert"; import fs from "fs"; import path from "path"; import os from "os"; import { RuntimeDepsService, type RuntimeDependencyPluginDefine } from "./runtime-deps-service.js"; -import { accessRegistry, pluginRegistry } from "@certd/pipeline"; -import { addonRegistry } from "@certd/lib-server"; +import { accessRegistry } from "../access/registry.js"; +import { pluginRegistry } from "../plugin/registry.js"; describe("RuntimeDepsService", () => { it("detects conflicting dependency ranges across plugins", () => { @@ -13,7 +13,6 @@ describe("RuntimeDepsService", () => { { name: "a", dependPackages: { foo: "^1.0.0" } }, { name: "b", dependPackages: { foo: "^1.2.0" } }, ]); - assert.deepEqual(merged.dependencies, { foo: "^1.0.0" }); assert.equal(merged.conflicts.length, 0); }); @@ -24,62 +23,45 @@ describe("RuntimeDepsService", () => { { name: "a", dependPackages: { foo: "^1.0.0" } }, { name: "b", dependPackages: { foo: "^2.0.0" } }, ]); - assert.equal(merged.conflicts.length, 1); assert.equal(merged.conflicts[0].packageName, "foo"); }); it("builds a runtime package manifest in the target directory", async () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-")); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; + const service = new RuntimeDepsService({ rootDir }); service.registryResolver = { - async resolve() { - return "https://registry.npmmirror.com"; - }, + async resolve() { return "https://registry.npmmirror.com"; }, } as any; service.commandRunner = { async run(command: string, args: string[]) { assert.equal(command, "pnpm"); - if (args.includes("--version")) { - return { stdout: "9.1.0\n", stderr: "", code: 0 }; - } + if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; } assert.equal(args[0], "install"); assert.ok(args.includes("--ignore-workspace")); assert.ok(args.includes("--no-frozen-lockfile")); return { stdout: "", stderr: "", code: 0 }; }, } as any; - const plugins: RuntimeDependencyPluginDefine[] = [{ name: "a", dependPackages: { foo: "^1.0.0" } }]; const result = await service.ensureInstalled({ plugins }); - assert.equal(result.registryUrl, "https://registry.npmmirror.com"); assert.ok(fs.existsSync(path.join(rootDir, "package.json"))); }); it("installs direct dependency maps without plugin metadata", async () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-direct-")); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - service.registryResolver = { - async resolve() { - return ""; - }, - } as any; + const service = new RuntimeDepsService({ rootDir }); + service.registryResolver = { async resolve() { return ""; } } as any; service.commandRunner = { async run(command: string, args: string[]) { assert.equal(command, "pnpm"); - if (args.includes("--version")) { - return { stdout: "9.1.0\n", stderr: "", code: 0 }; - } + if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; } fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true }); return { stdout: "", stderr: "", code: 0 }; }, } as any; - await service.ensureDependencies({ dependencies: { directPkg: "^1.0.0" } }); - const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8")); assert.deepEqual(manifest.dependencies, { directPkg: "^1.0.0" }); }); @@ -91,38 +73,20 @@ describe("RuntimeDepsService", () => { fs.writeFileSync(path.join(rootDir, "package.json"), JSON.stringify({ name: "runtime-root", type: "module" }), "utf8"); fs.writeFileSync(path.join(packageDir, "package.json"), JSON.stringify({ name: "runtime-only", type: "module", main: "index.js" }), "utf8"); fs.writeFileSync(path.join(packageDir, "index.js"), "export const value = 42;\n", "utf8"); - - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - service.commandRunner = { - async run() { - throw new Error("install should not run"); - }, - } as any; - + const service = new RuntimeDepsService({ rootDir }); + service.commandRunner = { async run() { throw new Error("install should not run"); } } as any; const mod = await service.importRuntime("runtime-only"); - assert.equal(mod.value, 42); }); it("installs configured lazy dependency when import target is missing", async () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-lazy-")); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - service.lazyDependencies = { - "lazy-pkg": "^1.2.3", - }; - service.registryResolver = { - async resolve() { - return ""; - }, - } as any; + const service = new RuntimeDepsService({ rootDir, lazyDependencies: { "lazy-pkg": "^1.2.3" } }); + service.registryResolver = { async resolve() { return ""; } } as any; service.commandRunner = { async run(command: string, args: string[]) { assert.equal(command, "pnpm"); - if (args.includes("--version")) { - return { stdout: "9.1.0\n", stderr: "", code: 0 }; - } + if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; } const packageDir = path.join(rootDir, "node_modules", "lazy-pkg", "sub"); fs.mkdirSync(packageDir, { recursive: true }); fs.writeFileSync(path.join(rootDir, "node_modules", "lazy-pkg", "package.json"), JSON.stringify({ name: "lazy-pkg", type: "module" }), "utf8"); @@ -130,9 +94,7 @@ describe("RuntimeDepsService", () => { return { stdout: "", stderr: "", code: 0 }; }, } as any; - const mod = await service.importRuntime("lazy-pkg/sub/entry.js"); - const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8")); assert.deepEqual(manifest.dependencies, { "lazy-pkg": "^1.2.3" }); assert.equal(mod.value, 7); @@ -140,22 +102,12 @@ describe("RuntimeDepsService", () => { it("resolves scoped package names for lazy imports", async () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-scoped-")); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - service.lazyDependencies = { - "@scope/lazy": "^2.0.0", - }; - service.registryResolver = { - async resolve() { - return ""; - }, - } as any; + const service = new RuntimeDepsService({ rootDir, lazyDependencies: { "@scope/lazy": "^2.0.0" } }); + service.registryResolver = { async resolve() { return ""; } } as any; service.commandRunner = { async run(command: string, args: string[]) { assert.equal(command, "pnpm"); - if (args.includes("--version")) { - return { stdout: "9.1.0\n", stderr: "", code: 0 }; - } + if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; } const packageDir = path.join(rootDir, "node_modules", "@scope", "lazy", "dist"); fs.mkdirSync(packageDir, { recursive: true }); fs.writeFileSync(path.join(rootDir, "node_modules", "@scope", "lazy", "package.json"), JSON.stringify({ name: "@scope/lazy", type: "module" }), "utf8"); @@ -163,9 +115,7 @@ describe("RuntimeDepsService", () => { return { stdout: "", stderr: "", code: 0 }; }, } as any; - const mod = await service.importRuntime("@scope/lazy/dist/index.js"); - const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8")); assert.deepEqual(manifest.dependencies, { "@scope/lazy": "^2.0.0" }); assert.equal(mod.scoped, true); @@ -173,82 +123,36 @@ describe("RuntimeDepsService", () => { it("reports missing lazy dependency configuration", async () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-lazy-missing-")); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - service.lazyDependencies = {}; - + const service = new RuntimeDepsService({ rootDir, lazyDependencies: {} }); await assert.rejects(() => service.importRuntime("missing-pkg/sub.js"), /未配置懒加载版本: missing-pkg/); }); it("falls back to project node_modules when lazy dependency is not configured", async () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-project-fallback-")); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - service.lazyDependencies = {}; - + const service = new RuntimeDepsService({ rootDir, lazyDependencies: {} }); const mod = await service.importRuntime("dayjs"); - - assert.equal(typeof mod.default, "function"); - }); - - it("falls back to project node_modules when lazy install fails", async () => { - const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-project-fallback-install-")); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - service.lazyDependencies = { - dayjs: "^1.11.7", - }; - service.registryResolver = { - async resolve() { - return ""; - }, - } as any; - service.commandRunner = { - async run(command: string, args: string[]) { - assert.equal(command, "pnpm"); - if (args.includes("--version")) { - return { stdout: "9.1.0\n", stderr: "", code: 0 }; - } - return { stdout: "", stderr: "install failed in test", code: 1 }; - }, - } as any; - - const mod = await service.importRuntime("dayjs"); - assert.equal(typeof mod.default, "function"); }); it("keeps previously installed dependencies when installing a later plugin", async () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-merge-")); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - service.registryResolver = { - async resolve() { - return ""; - }, - } as any; + const service = new RuntimeDepsService({ rootDir }); + service.registryResolver = { async resolve() { return ""; } } as any; service.commandRunner = { async run(command: string, args: string[]) { assert.equal(command, "pnpm"); - if (args.includes("--version")) { - return { stdout: "9.1.0\n", stderr: "", code: 0 }; - } + if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; } fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true }); return { stdout: "", stderr: "", code: 0 }; }, } as any; - await service.ensureInstalled({ plugins: [{ name: "a", pluginType: "deploy", dependPackages: { foo: "^1.0.0" } }] }); await service.ensureInstalled({ plugins: [{ name: "b", pluginType: "deploy", dependPackages: { bar: "^2.0.0" } }] }); - const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8")); - assert.deepEqual(manifest.dependencies, { - foo: "^1.0.0", - bar: "^2.0.0", - }); + assert.deepEqual(manifest.dependencies, { foo: "^1.0.0", bar: "^2.0.0" }); }); - it("includes npm dependencies from dependent plugins", () => { + it("includes npm dependencies from dependent plugins", async () => { const service = new RuntimeDepsService(); accessRegistry.register("runtimeDepsAccess", { define: { name: "runtimeDepsAccess", title: "access", dependPackages: { accessOnly: "^1.0.0" } } as any, @@ -256,17 +160,11 @@ describe("RuntimeDepsService", () => { }); try { const resolved = service.resolvePluginDependencies({ - name: "deploy", - pluginType: "deploy", - dependPlugins: { "access:runtimeDepsAccess": "*" }, + name: "deploy", pluginType: "deploy", dependPlugins: { "access:runtimeDepsAccess": "*" }, dependPackages: { deployOnly: "^1.0.0" }, }); const merged = service.collectDependencies(resolved); - - assert.deepEqual(merged.dependencies, { - deployOnly: "^1.0.0", - accessOnly: "^1.0.0", - }); + assert.deepEqual(merged.dependencies, { deployOnly: "^1.0.0", accessOnly: "^1.0.0" }); } finally { accessRegistry.unRegister("runtimeDepsAccess"); } @@ -274,19 +172,12 @@ describe("RuntimeDepsService", () => { it("installs dependencies by registered plugin key", async () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-key-")); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - service.registryResolver = { - async resolve() { - return ""; - }, - } as any; + const service = new RuntimeDepsService({ rootDir }); + service.registryResolver = { async resolve() { return ""; } } as any; service.commandRunner = { async run(command: string, args: string[]) { assert.equal(command, "pnpm"); - if (args.includes("--version")) { - return { stdout: "9.1.0\n", stderr: "", code: 0 }; - } + if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; } fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true }); return { stdout: "", stderr: "", code: 0 }; }, @@ -296,8 +187,8 @@ describe("RuntimeDepsService", () => { target: async () => ({} as any), }); try { + service.setRegistries({ pluginRegistry, accessRegistry }); await service.ensureRuntimeDependencies({ pluginKeys: "plugin:runtimeDepsKey" }); - const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8")); assert.deepEqual(manifest.dependencies, { keyed: "^1.0.0" }); } finally { @@ -305,50 +196,8 @@ describe("RuntimeDepsService", () => { } }); - it("installs dependencies from multiple plugin keys including addon subtype keys", async () => { - const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-keys-")); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - service.registryResolver = { - async resolve() { - return ""; - }, - } as any; - service.commandRunner = { - async run(command: string, args: string[]) { - assert.equal(command, "pnpm"); - if (args.includes("--version")) { - return { stdout: "9.1.0\n", stderr: "", code: 0 }; - } - fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true }); - return { stdout: "", stderr: "", code: 0 }; - }, - } as any; - accessRegistry.register("runtimeDepsArrayAccess", { - define: { name: "runtimeDepsArrayAccess", title: "access", dependPackages: { accessPkg: "^1.0.0" } } as any, - target: async () => ({} as any), - }); - addonRegistry.register("captcha:runtimeDepsArrayAddon", { - define: { addonType: "captcha", name: "runtimeDepsArrayAddon", title: "addon", dependPackages: { addonPkg: "^2.0.0" } } as any, - target: async () => ({} as any), - }); - try { - await service.ensureRuntimeDependencies({ pluginKeys: ["access:runtimeDepsArrayAccess", "addon:captcha:runtimeDepsArrayAddon"] }); - - const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8")); - assert.deepEqual(manifest.dependencies, { - accessPkg: "^1.0.0", - addonPkg: "^2.0.0", - }); - } finally { - accessRegistry.unRegister("runtimeDepsArrayAccess"); - addonRegistry.unRegister("captcha:runtimeDepsArrayAddon"); - } - }); - it("reports missing dependent plugins", () => { const service = new RuntimeDepsService(); - assert.throws(() => service.resolvePluginDependencies({ name: "deploy", pluginType: "deploy", dependPlugins: { "access:access": "*" } }), /插件依赖缺失/); }); @@ -360,12 +209,10 @@ describe("RuntimeDepsService", () => { }); try { assert.throws( - () => - service.resolvePluginDependencies({ - name: "deploy", - pluginType: "deploy", - dependPlugins: { "access:runtimeDepsVersionedAccess": "^2.0.0" }, - }), + () => service.resolvePluginDependencies({ + name: "deploy", pluginType: "deploy", + dependPlugins: { "access:runtimeDepsVersionedAccess": "^2.0.0" }, + }), /插件依赖版本冲突/ ); } finally { @@ -375,72 +222,9 @@ describe("RuntimeDepsService", () => { it("reports bare dependent plugin names as invalid format", () => { const service = new RuntimeDepsService(); - assert.throws(() => service.resolvePluginDependencies({ name: "deploy", pluginType: "deploy", dependPlugins: { runtimeDepsBareName: "*" } }), /插件依赖格式错误/); }); - it("records runtime install environment state", async () => { - const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-state-")); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - service.registryResolver = { - async resolve() { - return ""; - }, - } as any; - service.commandRunner = { - async run(command: string, args: string[]) { - assert.equal(command, "pnpm"); - if (args.includes("--version")) { - return { stdout: "9.1.0\n", stderr: "", code: 0 }; - } - assert.equal(args[0], "install"); - return { stdout: "", stderr: "", code: 0 }; - }, - } as any; - - await service.ensureInstalled({ plugins: [{ name: "a", dependPackages: { foo: "^1.0.0" } }] }); - - const state = JSON.parse(fs.readFileSync(path.join(rootDir, "install-state.json"), "utf8")); - assert.equal(state.nodeVersion, process.version); - assert.equal(state.pnpmVersion, "9.1.0"); - assert.equal(state.lastError, undefined); - }); - - it("serializes installs with a file lock", async () => { - const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-lock-")); - const serviceA = new RuntimeDepsService(); - const serviceB = new RuntimeDepsService(); - for (const service of [serviceA, serviceB]) { - service.runtimeDepsRootDir = rootDir; - service.registryResolver = { - async resolve() { - return ""; - }, - } as any; - } - let installCount = 0; - const commandRunner = { - async run(command: string, args: string[]) { - assert.equal(command, "pnpm"); - if (args.includes("--version")) { - return { stdout: "9.1.0\n", stderr: "", code: 0 }; - } - assert.equal(args[0], "install"); - installCount++; - await new Promise(resolve => setTimeout(resolve, 50)); - fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true }); - return { stdout: "", stderr: "", code: 0 }; - }, - }; - serviceA.commandRunner = commandRunner as any; - serviceB.commandRunner = commandRunner as any; - - await Promise.all([serviceA.ensureInstalled({ plugins: [{ name: "a", dependPackages: { foo: "^1.0.0" } }] }), serviceB.ensureInstalled({ plugins: [{ name: "a", dependPackages: { foo: "^1.0.0" } }] })]); - - assert.equal(installCount, 1); - }); - it("does not pass node debugger options to pnpm child process", async () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-env-")); const oldNodeOptions = process.env.NODE_OPTIONS; @@ -448,62 +232,30 @@ describe("RuntimeDepsService", () => { process.env.NODE_OPTIONS = "--inspect=127.0.0.1:9229 --max-old-space-size=4096"; process.env.VSCODE_INSPECTOR_OPTIONS = '{"inspectorIpc":"test"}'; try { - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - service.registryResolver = { - async resolve() { - return ""; - }, - } as any; + const service = new RuntimeDepsService({ rootDir }); + service.registryResolver = { async resolve() { return ""; } } as any; service.commandRunner = { async run(command: string, args: string[], options: { env?: NodeJS.ProcessEnv }) { assert.equal(options.env?.NODE_OPTIONS, "--max-old-space-size=4096"); assert.equal(options.env?.VSCODE_INSPECTOR_OPTIONS, undefined); assert.equal(options.env?.CI, "true"); assert.equal(options.env?.pnpm_config_confirm_modules_purge, "false"); - if (args.includes("--version")) { - return { stdout: "9.1.0\n", stderr: "", code: 0 }; - } + if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; } return { stdout: "", stderr: "", code: 0 }; }, } as any; - await service.ensureInstalled({ plugins: [{ name: "a", dependPackages: { foo: "^1.0.0" } }] }); } finally { - if (oldNodeOptions == null) { - delete process.env.NODE_OPTIONS; - } else { - process.env.NODE_OPTIONS = oldNodeOptions; - } - if (oldInspectorOptions == null) { - delete process.env.VSCODE_INSPECTOR_OPTIONS; - } else { - process.env.VSCODE_INSPECTOR_OPTIONS = oldInspectorOptions; - } + if (oldNodeOptions == null) { delete process.env.NODE_OPTIONS; } + else { process.env.NODE_OPTIONS = oldNodeOptions; } + if (oldInspectorOptions == null) { delete process.env.VSCODE_INSPECTOR_OPTIONS; } + else { process.env.VSCODE_INSPECTOR_OPTIONS = oldInspectorOptions; } } }); - it.skip("clears runtime dependency directory", async () => { - const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-clear-")); - const runtimeRootDir = path.join(rootDir, ".runtime-deps"); - fs.mkdirSync(path.join(runtimeRootDir, "node_modules", "foo"), { recursive: true }); - fs.writeFileSync(path.join(runtimeRootDir, "package.json"), "{}", "utf8"); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = runtimeRootDir; - service.installTimeoutMs = 1000; - - await service.clearRuntimeDeps(); - - assert.equal(fs.existsSync(runtimeRootDir), true); - const remainingEntries = fs.readdirSync(runtimeRootDir).filter(e => e !== ".install.lock"); - assert.equal(remainingEntries.length, 0); - }); - it("rejects clearing unexpected runtime dependency path", async () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-clear-invalid-")); - const service = new RuntimeDepsService(); - service.runtimeDepsRootDir = rootDir; - + const service = new RuntimeDepsService({ rootDir }); await assert.rejects(() => service.clearRuntimeDeps(), /动态依赖目录配置异常/); }); }); diff --git a/packages/ui/certd-server/src/modules/runtime-deps/runtime-deps-service.ts b/packages/core/pipeline/src/service/runtime-deps-service.ts similarity index 72% rename from packages/ui/certd-server/src/modules/runtime-deps/runtime-deps-service.ts rename to packages/core/pipeline/src/service/runtime-deps-service.ts index d6c7ef39b..8c7ea7a4c 100644 --- a/packages/ui/certd-server/src/modules/runtime-deps/runtime-deps-service.ts +++ b/packages/core/pipeline/src/service/runtime-deps-service.ts @@ -1,15 +1,28 @@ -import fs from "fs"; +import fs from "fs"; import path from "path"; import { spawn } from "child_process"; import crypto from "crypto"; -import { Config, Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core"; import { createRequire } from "module"; import { pathToFileURL } from "url"; -import { NpmRegistryResolver } from "./npm-registry-resolver.js"; -import { Registry, accessRegistry, notificationRegistry, pluginRegistry } from "@certd/pipeline"; -import { dnsProviderRegistry } from "@certd/plugin-lib"; -import { addonRegistry } from "@certd/lib-server"; -import { logger, ILogger } from "@certd/basic"; +import { logger as defaultLogger } from "@certd/basic"; +import type { Registry } from "../registry/registry.js"; +export type ILogger = { + info: (message: string) => void; + warn?: (message: string) => void; + error?: (message: string, ...args: any[]) => void; +}; + +export type ImportRuntime = (specifier: string, logger?: ILogger) => Promise; + +export type EnsureRuntimeDepsOptions = { + pluginKeys: string | string[]; + logger?: ILogger; +}; + +export interface IRuntimeDepsService { + ensureRuntimeDependencies(options: EnsureRuntimeDepsOptions): Promise; + importRuntime: ImportRuntime; +} export type RuntimeDependencyPluginDefine = { name: string; @@ -30,22 +43,6 @@ type RegisteredDefineLike = RuntimeDependencyPluginDefine & { dependPackages?: Record; }; -function normalizeRange(range: string) { - return range.trim().replace(/^\^/, "").replace(/^~?/, ""); -} - -function areRangesCompatible(a: string, b: string) { - if (!a || !b) { - return true; - } - if (a === "*" || b === "*") { - return true; - } - const left = normalizeRange(a).split("."); - const right = normalizeRange(b).split("."); - return left[0] === right[0]; -} - type DependencyConflict = { packageName: string; ranges: Array<{ pluginName: string; range: string }>; @@ -73,27 +70,111 @@ type CommandRunnerResult = { }; type CommandRunner = { - // @ts-ignore run(command: string, args: string[], options: { cwd: string; timeoutMs: number; env?: NodeJS.ProcessEnv }): Promise; }; +export type NpmRegistryResolverConfig = { + mode?: "auto" | "fixed" | "system"; + fixedUrl?: string; + candidates?: string[]; + probeTimeoutMs?: number; + cacheTtlMs?: number; +}; + +export type RegistryProbeResult = { + registryUrl: string; + ok: boolean; + elapsedMs: number; +}; + +export class NpmRegistryResolver { + config: NpmRegistryResolverConfig; + private cache?: { registryUrl: string; expiresAt: number }; + + constructor(config?: NpmRegistryResolverConfig) { + this.config = config || {}; + } + + async resolve(): Promise { + const config = this.config; + if (config?.mode === "fixed" && config.fixedUrl) { + return config.fixedUrl; + } + if (config?.mode === "system") { + return ""; + } + const cached = this.cache; + if (cached && cached.expiresAt > Date.now()) { + return cached.registryUrl; + } + const candidates = (config?.candidates || []).filter(Boolean); + if (candidates.length === 0) { + return ""; + } + const probes = await Promise.allSettled(candidates.map(registryUrl => this.probe(registryUrl))); + const okList = probes.map(item => (item.status === "fulfilled" ? item.value : null)).filter((item): item is RegistryProbeResult => !!item && item.ok); + if (okList.length > 0) { + okList.sort((a, b) => a.elapsedMs - b.elapsedMs); + const best = okList[0].registryUrl; + this.cache = { registryUrl: best, expiresAt: Date.now() + (config?.cacheTtlMs || 6 * 60 * 60 * 1000) }; + return best; + } + return ""; + } + + async probe(registryUrl: string): Promise { + const timeoutMs = this.config?.probeTimeoutMs || 3000; + const started = Date.now(); + try { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeoutMs); + try { + const res = await fetch(`${registryUrl.replace(/\/$/, "")}/-/ping`, { signal: controller.signal }); + return { registryUrl, ok: res.ok, elapsedMs: Date.now() - started }; + } finally { + clearTimeout(timer); + } + } catch { + return { registryUrl, ok: false, elapsedMs: Date.now() - started }; + } + } +} + +export type RuntimeDepsConfig = { + rootDir?: string; + autoInstall?: boolean; + enabled?: boolean; + installTimeoutMs?: number; + pnpmCommand?: string; + lazyDependencies?: Record; + registry?: NpmRegistryResolverConfig; +}; + +function normalizeRange(range: string) { + return range.trim().replace(/^\^/, "").replace(/^~?/, ""); +} + +function areRangesCompatible(a: string, b: string) { + if (!a || !b) { + return true; + } + if (a === "*" || b === "*") { + return true; + } + const left = normalizeRange(a).split("."); + const right = normalizeRange(b).split("."); + return left[0] === right[0]; +} + const PROCESS_LOCKS = new Map>(); class DefaultCommandRunner implements CommandRunner { - // @ts-ignore async run(command: string, args: string[], options: { cwd: string; timeoutMs: number; env?: NodeJS.ProcessEnv }): Promise { return await new Promise(resolve => { let stdout = ""; let stderr = ""; let settled = false; - const child = spawn(command, args, { - cwd: options.cwd, - env: options.env, - windowsHide: true, - // @ts-ignore - shell: process.platform === "win32", - }); - + const child = spawn(command, args, { cwd: options.cwd, env: options.env, windowsHide: true, shell: process.platform === "win32" }); const timer = setTimeout(() => { if (settled) { return; @@ -102,7 +183,6 @@ class DefaultCommandRunner implements CommandRunner { child.kill("SIGTERM"); resolve({ stdout, stderr: stderr || `command timeout after ${options.timeoutMs}ms`, code: 1 }); }, options.timeoutMs); - child.stdout?.on("data", chunk => { stdout += chunk.toString(); }); @@ -129,43 +209,55 @@ class DefaultCommandRunner implements CommandRunner { } } -@Provide() -@Scope(ScopeEnum.Singleton) export class RuntimeDepsService { - @Config("runtimeDeps.rootDir") - runtimeDepsRootDir = "./data/.runtime-deps"; - - @Config("runtimeDeps.autoInstall") - autoInstall = true; - - @Config("runtimeDeps.enabled") - enabled = true; - - @Config("runtimeDeps.installTimeoutMs") - installTimeoutMs = 120000; - - @Config("runtimeDeps.pnpmCommand") - pnpmCommand = ""; - - @Config("runtimeDeps.lazyDependencies") - lazyDependencies: Record = {}; - - /** - * 从插件 registry 收集到的懒加载依赖,键为包名,值为版本范围 - */ - pluginLazyDependencies: Record = {}; - - @Inject() + runtimeDepsRootDir: string; + autoInstall: boolean; + enabled: boolean; + installTimeoutMs: number; + pnpmCommand: string; + lazyDependencies: Record; registryResolver!: NpmRegistryResolver; - commandRunner: CommandRunner = new DefaultCommandRunner(); - + pluginLazyDependencies: Record = {}; private installPromises = new Map>(); + private registriesMap: Record; pluginType: string; addonType?: string }> | null = null; + + constructor(config: RuntimeDepsConfig, registries: any) { + this.runtimeDepsRootDir = config?.rootDir ?? "./data/.runtime-deps"; + this.autoInstall = config?.autoInstall ?? true; + this.enabled = config?.enabled ?? true; + this.installTimeoutMs = config?.installTimeoutMs ?? 120000; + this.pnpmCommand = config?.pnpmCommand ?? ""; + this.lazyDependencies = config?.lazyDependencies ?? {}; + this.registryResolver = new NpmRegistryResolver(config?.registry); + if (registries) { + this.setRegistries(registries); + } + } + + setRegistries(registries: { pluginRegistry?: Registry; accessRegistry?: Registry; notificationRegistry?: Registry; dnsProviderRegistry?: Registry; addonRegistry?: Registry }) { + const map: Record; pluginType: string; addonType?: string }> = {}; + if (registries.pluginRegistry) { + map["plugin"] = { registry: registries.pluginRegistry, pluginType: "plugin" }; + } + if (registries.accessRegistry) { + map["access"] = { registry: registries.accessRegistry, pluginType: "access" }; + } + if (registries.notificationRegistry) { + map["notification"] = { registry: registries.notificationRegistry, pluginType: "notification" }; + } + if (registries.dnsProviderRegistry) { + map["dnsProvider"] = { registry: registries.dnsProviderRegistry, pluginType: "dnsProvider" }; + } + if (registries.addonRegistry) { + map["addon"] = { registry: registries.addonRegistry, pluginType: "addon", addonType: "" }; + } + this.registriesMap = map; + } collectDependencies(plugins: RuntimeDependencyPluginDefine[]): CollectDependenciesResult { const merged: Record = {}; const seen: Record> = {}; - for (const plugin of plugins) { const deps = plugin.dependPackages || {}; for (const [packageName, range] of Object.entries(deps)) { @@ -173,7 +265,6 @@ export class RuntimeDepsService { seen[packageName].push({ pluginName: plugin.name, range }); } } - const conflicts: DependencyConflict[] = []; for (const [packageName, ranges] of Object.entries(seen)) { const first = ranges[0]?.range; @@ -187,7 +278,6 @@ export class RuntimeDepsService { } merged[packageName] = first; } - return { dependencies: merged, conflicts }; } @@ -204,16 +294,10 @@ export class RuntimeDepsService { async ensureDependencies(options: { dependencies: Record; logger?: ILogger }): Promise { const { dependencies, logger: log } = options; if (!this.enabled) { - return { - registryUrl: "", - packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json"), - }; + return { registryUrl: "", packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json") }; } if (!this.autoInstall) { - return { - registryUrl: "", - packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json"), - }; + return { registryUrl: "", packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json") }; } const dependenciesHash = this.createDependenciesHash(dependencies); let installPromise = this.installPromises.get(dependenciesHash); @@ -244,94 +328,21 @@ export class RuntimeDepsService { const keys = Array.isArray(pluginKeys) ? pluginKeys : [pluginKeys]; const pluginDefines = keys.map(pluginKey => this.getDefineByPluginKey(pluginKey)); if (pluginDefines.every(pluginDefine => !pluginDefine.dependPackages && !pluginDefine.dependPlugins)) { - return { - registryUrl: "", - packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json"), - }; + return { registryUrl: "", packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json") }; } const expandedPluginDefines = pluginDefines.flatMap(pluginDefine => this.resolvePluginDependencies(pluginDefine)); return await this.ensureInstalled({ plugins: expandedPluginDefines, logger: log }); } - private async doEnsureInstalled(options: { dependencies: Record; logger?: ILogger }): Promise { - let { dependencies } = options; - const log = options.logger || logger; - return await this.withInstallLock(async () => { - const rootDir = this.getRuntimeDepsRootDir(); - const packageJsonPath = path.join(rootDir, "package.json"); - const lockPath = path.join(rootDir, "pnpm-lock.yaml"); - log.info(`第三方依赖安装: ${JSON.stringify(dependencies)}`); - dependencies = this.mergeInstalledDependencies(this.readManifestDependencies(packageJsonPath), dependencies); - const dependenciesHash = this.createDependenciesHash(dependencies); - const statePath = path.join(rootDir, "install-state.json"); - const currentState = this.readInstallState(statePath); - if (currentState?.dependenciesHash === dependenciesHash && fs.existsSync(path.join(rootDir, "node_modules"))) { - log.info("第三方依赖已安装"); - return { registryUrl: currentState.registryUrl || "", packageJsonPath }; - } - const manifest = { - name: "certd-runtime-deps", - private: true, - type: "module", - dependencies, - }; - fs.writeFileSync(packageJsonPath, JSON.stringify(manifest, null, 2), "utf8"); - - const registryUrl = await this.registryResolver.resolve(); - const env = this.buildChildEnv(registryUrl); - const command = this.getPnpmCommand(); - const pnpmVersion = await this.getPnpmVersion(command, env); - const args = ["install", "--prod", "--ignore-scripts", "--ignore-workspace", "--no-frozen-lockfile", "--reporter=append-only"]; - if (registryUrl) { - args.push(`--registry=${registryUrl}`); - } - - log.info(`开始安装第三方依赖: ${Object.keys(dependencies).join(", ")}`); - const result = await this.commandRunner.run(command, args, { - cwd: rootDir, - timeoutMs: this.installTimeoutMs, - env, - }); - if (result.code !== 0) { - const message = result.stderr || result.stdout || "unknown error"; - this.writeInstallState(statePath, { - ...currentState, - installedAt: currentState?.installedAt, - failedAt: new Date().toISOString(), - registryUrl, - dependenciesHash, - // @ts-ignore - nodeVersion: process.version, - pnpmVersion, - lockFileExists: fs.existsSync(lockPath), - lastError: message, - }); - throw new Error(`动态依赖安装失败: ${message}`); - } - this.writeInstallState(statePath, { - installedAt: new Date().toISOString(), - registryUrl, - dependenciesHash, - // @ts-ignore - nodeVersion: process.version, - pnpmVersion, - lockFileExists: fs.existsSync(lockPath), - }); - log.info("第三方依赖安装完成"); - return { registryUrl, packageJsonPath }; - }); - } - - async importRuntime(specifier: string, logger?: ILogger) { + async importRuntime(specifier: string, logger: ILogger = defaultLogger) { if (this.isNativeImportSpecifier(specifier)) { return await import(specifier); } - const resolved = await this.resolveImportSpecifier(specifier, logger); return await import(pathToFileURL(resolved).href); } - private async resolveImportSpecifier(specifier: string, logger?: ILogger) { + private async resolveImportSpecifier(specifier: string, logger: ILogger = defaultLogger) { try { return this.resolveRuntimeSpecifier(specifier).resolved; } catch (runtimeError: any) { @@ -410,10 +421,7 @@ export class RuntimeDepsService { if (!range) { throw new Error(`动态依赖未安装且未配置懒加载版本: ${packageName}`); } - const dependencies = { - [packageName]: range, - }; - await this.ensureDependencies({ dependencies, logger }); + await this.ensureDependencies({ dependencies: { [packageName]: range }, logger }); } private isModuleNotFoundError(error: any) { @@ -423,7 +431,6 @@ export class RuntimeDepsService { resolvePluginDependencies(current: RuntimeDependencyPluginDefine): RuntimeDependencyPluginDefine[] { const resolved: RuntimeDependencyPluginDefine[] = []; const visited = new Set(); - const visit = (item: RuntimeDependencyPluginDefine) => { const key = this.buildPluginDependencyKey(item); if (visited.has(key)) { @@ -439,7 +446,6 @@ export class RuntimeDepsService { visit(dependency); } }; - visit(current); return resolved; } @@ -454,35 +460,81 @@ export class RuntimeDepsService { private getDefineByPluginKey(pluginKey: string, owner?: RuntimeDependencyPluginDefine): RuntimeDependencyPluginDefine { const parts = pluginKey.split(":"); - const [pluginType, subtype, rawName] = parts; - let name = rawName; + let pluginType: string, name: string, subtype: string | undefined; if (parts.length === 2) { - name = subtype; + [pluginType, name] = parts; } else if (parts.length === 3) { - //无修改 + [pluginType, subtype, name] = parts; } else { const ownerName = owner?.name || pluginKey; - throw new Error(`插件依赖格式错误: ${ownerName} 依赖 ${pluginKey},请使用 plugin:name、access:name、notification:name、dnsProvider:name 或 addon:subtype:name 格式`); + throw new Error(`插件依赖格式错误: ${ownerName} 依赖 ${pluginKey}`); } - const registryMap: Record; key: string; pluginType: string; addonType?: string }> = { - plugin: { registry: pluginRegistry, key: name, pluginType: "plugin" }, - access: { registry: accessRegistry, key: name, pluginType: "access" }, - notification: { registry: notificationRegistry, key: name, pluginType: "notification" }, - dnsProvider: { registry: dnsProviderRegistry, key: name, pluginType: "dnsProvider" }, - addon: { registry: addonRegistry, key: `${subtype}:${name}`, pluginType: "addon", addonType: subtype }, - }; - const target = registryMap[pluginType]; + if (!this.registriesMap) { + throw new Error("注册表未设置,请先调用 setRegistries"); + } + const target = this.registriesMap[pluginType]; if (!target) { const ownerName = owner?.name || pluginKey; throw new Error(`插件依赖格式错误: ${ownerName} 依赖 ${pluginKey},未知插件类型 ${pluginType}`); } - const define = target.registry.getDefine(target.key) as RegisteredDefineLike; + // addon 类型的 key 需要包含 subtype + const registryKey = pluginType === "addon" && subtype ? `${subtype}:${name}` : name; + const define = target.registry.getDefine(registryKey) as RegisteredDefineLike; if (!define) { throw new Error(`插件依赖缺失: ${owner?.name || pluginKey} 依赖 ${pluginKey},但该插件未注册或已禁用`); } return { ...define, key: pluginKey, pluginType: target.pluginType, addonType: target.addonType }; } + private async doEnsureInstalled(options: { dependencies: Record; logger?: ILogger }): Promise { + let { dependencies } = options; + const log = options.logger || defaultLogger; + return await this.withInstallLock(async () => { + const rootDir = this.getRuntimeDepsRootDir(); + const packageJsonPath = path.join(rootDir, "package.json"); + const lockPath = path.join(rootDir, "pnpm-lock.yaml"); + log.info(`第三方依赖安装: ${JSON.stringify(dependencies)}`); + dependencies = this.mergeInstalledDependencies(this.readManifestDependencies(packageJsonPath), dependencies); + const dependenciesHash = this.createDependenciesHash(dependencies); + const statePath = path.join(rootDir, "install-state.json"); + const currentState = this.readInstallState(statePath); + if (currentState?.dependenciesHash === dependenciesHash && fs.existsSync(path.join(rootDir, "node_modules"))) { + log.info("第三方依赖已安装"); + return { registryUrl: currentState.registryUrl || "", packageJsonPath }; + } + const manifest = { name: "certd-runtime-deps", private: true, type: "module", dependencies }; + fs.writeFileSync(packageJsonPath, JSON.stringify(manifest, null, 2), "utf8"); + const registryUrl = await this.registryResolver.resolve(); + const env = this.buildChildEnv(registryUrl); + const command = this.getPnpmCommand(); + const pnpmVersion = await this.getPnpmVersion(command, env); + const args = ["install", "--prod", "--ignore-scripts", "--ignore-workspace", "--no-frozen-lockfile", "--reporter=append-only"]; + if (registryUrl) { + args.push(`--registry=${registryUrl}`); + } + log.info(`开始安装第三方依赖: ${Object.keys(dependencies).join(", ")}`); + const result = await this.commandRunner.run(command, args, { cwd: rootDir, timeoutMs: this.installTimeoutMs, env }); + if (result.code !== 0) { + const message = result.stderr || result.stdout || "unknown error"; + this.writeInstallState(statePath, { + ...currentState, + installedAt: currentState?.installedAt, + failedAt: new Date().toISOString(), + registryUrl, + dependenciesHash, + nodeVersion: process.version, + pnpmVersion, + lockFileExists: fs.existsSync(lockPath), + lastError: message, + }); + throw new Error(`动态依赖安装失败: ${message}`); + } + this.writeInstallState(statePath, { installedAt: new Date().toISOString(), registryUrl, dependenciesHash, nodeVersion: process.version, pnpmVersion, lockFileExists: fs.existsSync(lockPath) }); + log.info("第三方依赖安装完成"); + return { registryUrl, packageJsonPath }; + }); + } + private async withInstallLock(run: () => Promise): Promise { const rootDir = this.getRuntimeDepsRootDir(); fs.mkdirSync(rootDir, { recursive: true }); @@ -508,9 +560,7 @@ export class RuntimeDepsService { } catch { try { fs.rmSync(lockFile, { force: true }); - } catch { - // Windows 下 closeSync 后文件句柄可能未立即释放,忽略清理失败 - } + } catch {} } } releaseProcessLock(); @@ -525,7 +575,6 @@ export class RuntimeDepsService { while (true) { try { const fd = fs.openSync(lockFile, "wx"); - // @ts-ignore fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }), "utf8"); return fd; } catch (error: any) { @@ -562,8 +611,7 @@ export class RuntimeDepsService { if (entry === ".install.lock") { continue; } - const entryPath = path.join(rootDir, entry); - fs.rmSync(entryPath, { recursive: true, force: true }); + fs.rmSync(path.join(rootDir, entry), { recursive: true, force: true }); } } this.installPromises.clear(); @@ -571,28 +619,16 @@ export class RuntimeDepsService { }); } - /** - * 合并 package.json 的 lazyDependencies 和插件注册表收集到的 dependPackages - * 插件依赖优先(同名时使用插件声明的版本) - */ getMergedLazyDependencies(): Record { return { ...this.lazyDependencies, ...this.pluginLazyDependencies }; } - /** - * 从所有插件注册表中收集 dependPackages,合并到 pluginLazyDependencies - */ - collectPluginDeps() { - const registries: Array<{ registry: Registry; keyFormatter?: (name: string) => string }> = [ - { registry: pluginRegistry }, - { registry: accessRegistry }, - { registry: notificationRegistry }, - { registry: dnsProviderRegistry }, - { registry: addonRegistry }, - ]; - + collectPluginDeps(logger?: ILogger) { + if (!this.registriesMap) { + return; + } const deps: Record = {}; - for (const { registry } of registries) { + for (const { registry } of Object.values(this.registriesMap)) { const defineList = registry.getDefineList(); for (const define of defineList) { const dependPackages = (define as any).dependPackages as Record | undefined; @@ -602,23 +638,19 @@ export class RuntimeDepsService { for (const [pkgName, range] of Object.entries(dependPackages)) { const existing = deps[pkgName]; if (existing && !areRangesCompatible(existing, range)) { - logger.warn(`懒加载依赖版本冲突: ${pkgName} => ${existing} vs ${range},保留已有版本`); + (logger || defaultLogger).warn?.(`懒加载依赖版本冲突: ${pkgName} => ${existing} vs ${range},保留已有版本`); continue; } deps[pkgName] = range; } } } - this.pluginLazyDependencies = deps; - logger.info(`从插件注册表收集到 ${Object.keys(deps).length} 个懒加载依赖`); + (logger || defaultLogger).info(`从插件注册表收集到 ${Object.keys(deps).length} 个懒加载依赖`); } - /** - * 刷新插件懒加载依赖:收集所有插件的 dependPackages 到内存列表,实际安装延迟到 importRuntime 时触发 - */ - async refreshPluginDeps() { - this.collectPluginDeps(); + refreshPluginDeps(logger?: ILogger) { + this.collectPluginDeps(logger); } private readInstallState(statePath: string): any { @@ -660,14 +692,8 @@ export class RuntimeDepsService { return dependencies; } - // @ts-ignore private async getPnpmVersion(command: string, env: NodeJS.ProcessEnv) { - const rootDir = this.getRuntimeDepsRootDir(); - const result = await this.commandRunner.run(command, ["--version"], { - cwd: rootDir, - timeoutMs: Math.min(this.installTimeoutMs, 10000), - env, - }); + const result = await this.commandRunner.run(command, ["--version"], { cwd: this.getRuntimeDepsRootDir(), timeoutMs: Math.min(this.installTimeoutMs, 10000), env }); if (result.code !== 0) { return ""; } @@ -675,14 +701,10 @@ export class RuntimeDepsService { } private getPnpmCommand() { - if (this.pnpmCommand) { - return this.pnpmCommand; - } - return "pnpm"; + return this.pnpmCommand || "pnpm"; } private buildChildEnv(registryUrl: string) { - // @ts-ignore const env = { ...process.env }; for (const key of ["NODE_OPTIONS", "VSCODE_INSPECTOR_OPTIONS", "NODE_INSPECTOR_PORT", "NODE_DEBUG"]) { if (!env[key]) { @@ -713,7 +735,7 @@ export class RuntimeDepsService { .join(" "); } - private getRuntimeDepsRootDir() { + getRuntimeDepsRootDir() { return path.resolve(this.runtimeDepsRootDir); } @@ -731,3 +753,21 @@ function isPluginVersionCompatible(plugin: RuntimeDependencyPluginDefine, expect } return areRangesCompatible(expectedRange, plugin.version); } + +let runtimeDepsServiceInstance: RuntimeDepsService | null = null; + +export function initRuntimeDepsService(config: RuntimeDepsConfig, registries: any): RuntimeDepsService { + runtimeDepsServiceInstance = new RuntimeDepsService(config, registries); + return runtimeDepsServiceInstance; +} + +export function getRuntimeDepsService(): RuntimeDepsService { + if (!runtimeDepsServiceInstance) { + throw new Error("RuntimeDepsService 未初始化"); + } + return runtimeDepsServiceInstance!; +} + +export async function importRuntime(specifier: string, logger: ILogger = defaultLogger): Promise { + return getRuntimeDepsService().importRuntime(specifier, logger); +} diff --git a/packages/core/pipeline/src/service/runtime.ts b/packages/core/pipeline/src/service/runtime.ts index 38207bac8..1ded20de5 100644 --- a/packages/core/pipeline/src/service/runtime.ts +++ b/packages/core/pipeline/src/service/runtime.ts @@ -1,27 +1,3 @@ -/** - * 运行时动态导入函数类型 - */ -export type ImportRuntime = (specifier: string, logger?: ILogger) => Promise; - -/** - * 日志接口 - */ -export type ILogger = { - info: (message: string) => void; -}; - -/** - * 运行时依赖服务参数 - */ -export type EnsureRuntimeDepsOptions = { - pluginKeys: string | string[]; - logger?: ILogger; -}; - -/** - * 运行时依赖服务接口 - */ -export interface IRuntimeDepsService { - ensureRuntimeDependencies(options: EnsureRuntimeDepsOptions): Promise; - importRuntime: ImportRuntime; -} +// 类型已迁至 runtime-deps-service.ts,通过 index.ts 重新导出 +// 此文件保留空壳,避免已有引用报错 +export * from "./runtime-deps-service.js"; diff --git a/packages/libs/lib-server/src/user/addon/api/api.ts b/packages/libs/lib-server/src/user/addon/api/api.ts index b5ccf937f..3c0200f09 100644 --- a/packages/libs/lib-server/src/user/addon/api/api.ts +++ b/packages/libs/lib-server/src/user/addon/api/api.ts @@ -1,15 +1,6 @@ import { HttpClient, ILogger, utils } from "@certd/basic"; -import {upperFirst} from "lodash-es"; -import { - accessRegistry, - FormItemProps, - IAccessService, - IRuntimeDepsService, - IServiceGetter, - PluginRequestHandleReq, - Registrable -} from "@certd/pipeline"; - +import { upperFirst } from "lodash-es"; +import { accessRegistry, FormItemProps, IAccessService, IServiceGetter, PluginRequestHandleReq, Registrable, getRuntimeDepsService } from "@certd/pipeline"; export type AddonRequestHandleReqInput = { id?: number; @@ -19,7 +10,7 @@ export type AddonRequestHandleReqInput = { export type AddonRequestHandleReq = { addonType: string; -} &PluginRequestHandleReq>; +} & PluginRequestHandleReq>; export type AddonInputDefine = FormItemProps & { title: string; @@ -48,8 +39,6 @@ export type AddonInstanceConfig = { }; }; - - export interface IAddon { ctx: AddonContext; [key: string]: any; @@ -67,13 +56,9 @@ export abstract class BaseAddon implements IAddon { ctx!: AddonContext; http!: HttpClient; logger!: ILogger; - runtimeDepsService?: IRuntimeDepsService; async importRuntime(specifier: string) { - if (!this.runtimeDepsService) { - return await import(specifier); - } - return await this.runtimeDepsService.importRuntime(specifier, this.logger); + return await getRuntimeDepsService().importRuntime(specifier, this.logger); } title!: string; @@ -85,7 +70,7 @@ export abstract class BaseAddon implements IAddon { if (accessId == null) { throw new Error("您还没有配置授权"); } - const accessService = await this.ctx.serviceGetter.get("accessService") + const accessService = await this.ctx.serviceGetter.get("accessService"); let res: any = null; if (isCommon) { res = await accessService.getCommonById(accessId); @@ -118,15 +103,12 @@ export abstract class BaseAddon implements IAddon { this.ctx = ctx; this.http = ctx.http; this.logger = ctx.logger; - if (!this.runtimeDepsService && this.ctx.serviceGetter) { - this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService"); - } } - setDefine = (define:AddonDefine) => { + setDefine = (define: AddonDefine) => { this.define = define; }; - async onRequest(req:AddonRequestHandleReq) { + async onRequest(req: AddonRequestHandleReq) { if (!req.action) { throw new Error("action is required"); } @@ -144,10 +126,8 @@ export abstract class BaseAddon implements IAddon { } throw new Error(`action ${req.action} not found`); } - } - export interface IAddonGetter { getById(id: any): Promise; getCommonById(id: any): Promise; diff --git a/packages/plugins/plugin-lib/src/cert/dns-provider/base.ts b/packages/plugins/plugin-lib/src/cert/dns-provider/base.ts index 45e1c2933..ebf928102 100644 --- a/packages/plugins/plugin-lib/src/cert/dns-provider/base.ts +++ b/packages/plugins/plugin-lib/src/cert/dns-provider/base.ts @@ -1,5 +1,5 @@ import { HttpClient, ILogger } from "@certd/basic"; -import { IAccessService, IRuntimeDepsService, PageRes, PageSearch } from "@certd/pipeline"; +import { IAccessService, PageRes, PageSearch, getRuntimeDepsService } from "@certd/pipeline"; import punycode from "punycode.js"; import { CreateRecordOptions, DnsProviderContext, DnsProviderDefine, DnsResolveRecord, DomainRecord, IDnsProvider, RemoveRecordOptions } from "./api.js"; import { dnsProviderRegistry } from "./registry.js"; @@ -7,13 +7,9 @@ export abstract class AbstractDnsProvider implements IDnsProvider { ctx!: DnsProviderContext; http!: HttpClient; logger!: ILogger; - runtimeDepsService?: IRuntimeDepsService; async importRuntime(specifier: string) { - if (!this.runtimeDepsService) { - throw new Error("runtimeDepsService 未初始化"); - } - return await this.runtimeDepsService.importRuntime(specifier, this.logger); + return await getRuntimeDepsService().importRuntime(specifier, this.logger); } usePunyCode(): boolean { @@ -42,9 +38,6 @@ export abstract class AbstractDnsProvider implements IDnsProvider { this.ctx = ctx; this.logger = ctx.logger; this.http = ctx.http; - if (!this.runtimeDepsService && this.ctx.serviceGetter) { - this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService"); - } } async parseDomain(fullDomain: string) { diff --git a/packages/ui/certd-client/package.json b/packages/ui/certd-client/package.json index 15a72f3d5..717ebf026 100644 --- a/packages/ui/certd-client/package.json +++ b/packages/ui/certd-client/package.json @@ -59,7 +59,6 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "core-js": "^3.36.0", - "cos-js-sdk-v5": "^1.7.0", "cron-parser": "^4.9.0", "cropperjs": "^1.6.1", "cssnano": "^7.0.6", diff --git a/packages/ui/certd-server/.eslintrc b/packages/ui/certd-server/.eslintrc index 7ddaf032e..8b68bd3a1 100644 --- a/packages/ui/certd-server/.eslintrc +++ b/packages/ui/certd-server/.eslintrc @@ -20,7 +20,6 @@ "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-this-alias": "off", - // 允许any - "@typescript-eslint/no-unsafe-anyassignment": "off" + "@typescript-eslint/no-unsafe-assignment": "off" } } diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json index a9bc7b9fb..fbceace15 100644 --- a/packages/ui/certd-server/package.json +++ b/packages/ui/certd-server/package.json @@ -25,6 +25,7 @@ "format": "prettier --write src", "lint": "mwts fix", "ci": "pnpm run cov", + "lint2": "cross-env NODE_ENV=production mwtsc -p tsconfig.build.json", "build-only": "cross-env NODE_ENV=production mwtsc -p tsconfig.build.json --cleanOutDir --skipLibCheck", "build": "pnpm run build-only && pnpm run export-metadata", "export-metadata": "node export-plugin-yaml.js", @@ -133,6 +134,7 @@ "rimraf": "^5.0.5", "ts-node": "^10.9.2", "tslib": "^2.8.1", + "eslint":"^7.32.0", "typescript": "^5.4.2", "why-is-node-running": "^3.2.2" }, diff --git a/packages/ui/certd-server/src/controller/sys/settings/sys-settings-controller.ts b/packages/ui/certd-server/src/controller/sys/settings/sys-settings-controller.ts index af8e1f30d..753b8dcc7 100644 --- a/packages/ui/certd-server/src/controller/sys/settings/sys-settings-controller.ts +++ b/packages/ui/certd-server/src/controller/sys/settings/sys-settings-controller.ts @@ -1,3 +1,4 @@ +import { getRuntimeDepsService } from "@certd/pipeline"; import { ALL, Body, Controller, Inject, Post, Provide, Query, RequestIP } from "@midwayjs/core"; import { addonRegistry, AddonService, CrudController, SysPrivateSettings, SysPublicSettings, SysSafeSetting, SysSettingsEntity, SysSettingsService } from "@certd/lib-server"; import { cloneDeep, merge } from "lodash-es"; @@ -7,7 +8,6 @@ import { getEmailSettings } from "../../../modules/sys/settings/fix.js"; import { http, logger, utils } from "@certd/basic"; import { CodeService } from "../../../modules/basic/service/code-service.js"; import { SmsServiceFactory } from "../../../modules/basic/sms/factory.js"; -import { RuntimeDepsService } from "../../../modules/runtime-deps/runtime-deps-service.js"; /** */ @@ -24,10 +24,7 @@ export class SysSettingsController extends CrudController { codeService: CodeService; @Inject() addonService: AddonService; - @Inject() - runtimeDepsService: RuntimeDepsService; - - getService() { +getService() { return this.service; } @@ -222,7 +219,12 @@ export class SysSettingsController extends CrudController { @Post("/clearRuntimeDeps", { description: "sys:settings:edit" }) async clearRuntimeDeps() { - await this.runtimeDepsService.clearRuntimeDeps(); + await getRuntimeDepsService().clearRuntimeDeps(); return this.ok(true); } } + + + + + diff --git a/packages/ui/certd-server/src/modules/auto/auto-a-register.ts b/packages/ui/certd-server/src/modules/auto/auto-a-register.ts index 315822905..25e1e89f5 100644 --- a/packages/ui/certd-server/src/modules/auto/auto-a-register.ts +++ b/packages/ui/certd-server/src/modules/auto/auto-a-register.ts @@ -1,15 +1,16 @@ -import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/core"; +import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/core"; +import { AutoCron } from "./auto-cron.js"; import { AutoInitSite } from "./auto-init-site.js"; import { AutoLoadPlugins } from "./auto-load-plugins.js"; -import { AutoCron } from "./auto-cron.js"; import { AutoMitterRegister } from "./auto-mitter-register.js"; import { AutoPipelineEmitterRegister } from "./auto-pipeline-emitter-register.js"; -import { AutoFix } from "./fix/auto-fix.js"; import { AutoPrint } from "./auto-print.js"; +import { AutoFix } from "./fix/auto-fix.js"; @Autoload() @Scope(ScopeEnum.Request, { allowDowngrade: true }) export class AutoARegister { + //这个A是必须,让他排在第一个 进行init,否则会被其他init模块抢先注册导致报错 @Inject() autoInitSite: AutoInitSite; @@ -43,3 +44,4 @@ export class AutoARegister { await this.autoPrint.init(); } } + diff --git a/packages/ui/certd-server/src/modules/auto/auto-load-plugins.ts b/packages/ui/certd-server/src/modules/auto/auto-load-plugins.ts index 3b9ed1b84..e4b8d9052 100644 --- a/packages/ui/certd-server/src/modules/auto/auto-load-plugins.ts +++ b/packages/ui/certd-server/src/modules/auto/auto-load-plugins.ts @@ -1,8 +1,11 @@ -import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core"; +import { Config, Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core"; import { logger } from "@certd/basic"; import { PluginService } from "../plugin/service/plugin-service.js"; import { registerPaymentProviders } from "../suite/payments/index.js"; -import { RuntimeDepsService } from "../runtime-deps/runtime-deps-service.js"; +import { getRuntimeDepsService, initRuntimeDepsService } from "@certd/pipeline"; +import { pluginRegistry, accessRegistry, notificationRegistry } from "@certd/pipeline"; +import { dnsProviderRegistry } from "@certd/plugin-lib"; +import { addonRegistry } from "@certd/lib-server"; @Provide() @Scope(ScopeEnum.Request, { allowDowngrade: true }) @@ -10,8 +13,8 @@ export class AutoLoadPlugins { @Inject() pluginService: PluginService; - @Inject() - runtimeDepsService: RuntimeDepsService; + @Config('runtimeDeps') + runtimeDepsConfig: any; async init() { logger.info(`加载插件开始,加载模式:${process.env.certd_plugin_loadmode}`); @@ -31,11 +34,23 @@ export class AutoLoadPlugins { } // await import("../../plugins/index.js") await this.pluginService.registerFromDb(); - + await registerPaymentProviders(); logger.info(`加载插件完成,加载模式:${process.env.certd_plugin_loadmode}`); + //初始化第三方依赖服务 + initRuntimeDepsService(this.runtimeDepsConfig,{ + pluginRegistry, + accessRegistry, + notificationRegistry, + dnsProviderRegistry, + addonRegistry, + }); // 收集插件 dependPackages 并安装 - await this.runtimeDepsService.refreshPluginDeps(); + const service = getRuntimeDepsService(); + service.refreshPluginDeps(); } } + + + diff --git a/packages/ui/certd-server/src/modules/basic/service/code-service.ts b/packages/ui/certd-server/src/modules/basic/service/code-service.ts index 76b2486ae..e3f8b3016 100644 --- a/packages/ui/certd-server/src/modules/basic/service/code-service.ts +++ b/packages/ui/certd-server/src/modules/basic/service/code-service.ts @@ -1,4 +1,4 @@ -import { cache, isDev, randomNumber, simpleNanoId } from "@certd/basic"; +import { cache, isDev, randomNumber, simpleNanoId } from "@certd/basic"; import { AccessService, AccessSysGetter, CodeErrorException, SysSettingsService } from "@certd/lib-server"; import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core"; import { ISmsService } from "../sms/api.js"; @@ -6,7 +6,7 @@ import { SmsServiceFactory } from "../sms/factory.js"; import { CaptchaService } from "./captcha-service.js"; import { EmailService } from "./email-service.js"; import { CaptchaRequest } from "../../../plugins/plugin-captcha/api.js"; -import { RuntimeDepsService } from "../../runtime-deps/runtime-deps-service.js"; + // {data: '', text: 'abcd'} /** @@ -25,10 +25,7 @@ export class CodeService { @Inject() captchaService: CaptchaService; - @Inject() - runtimeDepsService: RuntimeDepsService; - - async checkCaptcha(body: any, req: CaptchaRequest) { +async checkCaptcha(body: any, req: CaptchaRequest) { return await this.captchaService.doValidate({ form: body, req }); } /** @@ -60,7 +57,6 @@ export class CodeService { await sender.setCtx({ accessService: accessGetter, config: smsConfig, - runtimeDepsService: this.runtimeDepsService, }); const smsCode = randomNumber(verificationCodeLength); await sender.sendSmsCode({ @@ -200,3 +196,7 @@ export class CodeService { return cache.get(this.buildValidationValueKey(code)); } } + + + + diff --git a/packages/ui/certd-server/src/modules/basic/sms/api.ts b/packages/ui/certd-server/src/modules/basic/sms/api.ts index 930c665e6..1dde3e055 100644 --- a/packages/ui/certd-server/src/modules/basic/sms/api.ts +++ b/packages/ui/certd-server/src/modules/basic/sms/api.ts @@ -1,9 +1,8 @@ -import { FormItemProps, IAccessService } from "@certd/pipeline"; -import type { RuntimeDepsService } from "../../runtime-deps/runtime-deps-service.js"; +import { FormItemProps, IAccessService } from "@certd/pipeline"; export interface ISmsService { sendSmsCode(opts: { mobile: string; code: string; phoneCode: string }): Promise; - setCtx(ctx: { accessService: IAccessService; config: { [key: string]: any }; runtimeDepsService?: RuntimeDepsService }): Promise; + setCtx(ctx: { accessService: IAccessService; config: { [key: string]: any } }): Promise; } export type PluginInputs = { @@ -13,5 +12,4 @@ export type PluginInputs = { export type SmsPluginCtx = { accessService: IAccessService; config: T; - runtimeDepsService?: RuntimeDepsService; }; diff --git a/packages/ui/certd-server/src/modules/basic/sms/tencent-sms.ts b/packages/ui/certd-server/src/modules/basic/sms/tencent-sms.ts index fba25b27f..e24fb3d7b 100644 --- a/packages/ui/certd-server/src/modules/basic/sms/tencent-sms.ts +++ b/packages/ui/certd-server/src/modules/basic/sms/tencent-sms.ts @@ -1,5 +1,6 @@ -import { TencentAccess } from "../../../plugins/plugin-lib/tencent/access.js"; -import { ISmsService, PluginInputs, SmsPluginCtx } from "./api.js"; +import { TencentAccess } from "../../../plugins/plugin-lib/tencent/access.js"; +import { importRuntime } from "@certd/pipeline"; +import { ISmsService, PluginInputs } from "./api.js"; export type TencentSmsConfig = { accessId: string; @@ -66,26 +67,16 @@ export class TencentSmsService implements ISmsService { }; } - ctx: SmsPluginCtx; + ctx: { accessService: any; config: TencentSmsConfig }; async setCtx(ctx: any) { this.ctx = ctx; - if (this.ctx.runtimeDepsService) { - await this.ctx.runtimeDepsService.ensureDependencies({ - dependencies: { - "tencentcloud-sdk-nodejs": "^4.1.112", - }, - }); - } } async getClient() { - if (!this.ctx.runtimeDepsService) { - throw new Error("动态依赖服务未初始化,无法加载腾讯云短信SDK"); - } - const sdk = await this.ctx.runtimeDepsService.importRuntime("tencentcloud-sdk-nodejs/tencentcloud/services/sms/v20210111/index.js"); + const sdk = await importRuntime("tencentcloud-sdk-nodejs/tencentcloud/services/sms/v20210111/index.js"); const client = sdk.v20210111.Client; - const access = await this.ctx.accessService.getById(this.ctx.config.accessId); + const access: TencentAccess = await this.ctx.accessService.getById(this.ctx.config.accessId); // const region = this.region; const clientConfig = { @@ -126,3 +117,5 @@ export class TencentSmsService implements ISmsService { } } } + + diff --git a/packages/ui/certd-server/src/modules/mine/service/two-factor-service.ts b/packages/ui/certd-server/src/modules/mine/service/two-factor-service.ts index 8075afc62..2947efdb9 100644 --- a/packages/ui/certd-server/src/modules/mine/service/two-factor-service.ts +++ b/packages/ui/certd-server/src/modules/mine/service/two-factor-service.ts @@ -1,8 +1,9 @@ +import { importRuntime } from "@certd/pipeline"; import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core"; import { UserSettingsService } from "./user-settings-service.js"; import { UserTwoFactorSetting } from "./models.js"; import { UserService } from "../../sys/authority/service/user-service.js"; -import { RuntimeDepsService } from "../../runtime-deps/runtime-deps-service.js"; + /** * 授权 @@ -14,15 +15,12 @@ export class TwoFactorService { userSettingsService: UserSettingsService; @Inject() userService: UserService; - @Inject() - runtimeDepsService: RuntimeDepsService; - - async getAuthenticatorQrCode(userId: any) { +async getAuthenticatorQrCode(userId: any) { const setting = await this.getSetting(userId); const authenticatorSetting = setting.authenticator; if (!authenticatorSetting.secret) { - const { authenticator } = await this.runtimeDepsService.importRuntime("otplib"); + const { authenticator } = await importRuntime("otplib"); authenticatorSetting.secret = authenticator.generateSecret(); await this.userSettingsService.saveSetting(userId, null, setting); @@ -41,7 +39,7 @@ export class TwoFactorService { async saveAuthenticator(req: { userId: any; verifyCode: any }) { const userId = req.userId; - const { authenticator } = await this.runtimeDepsService.importRuntime("otplib"); + const { authenticator } = await importRuntime("otplib"); const setting = await this.getSetting(userId); const authenticatorSetting = setting.authenticator; @@ -80,7 +78,7 @@ export class TwoFactorService { } async verifyAuthenticatorCode(userId: any, verifyCode: string) { - const { authenticator } = await this.runtimeDepsService.importRuntime("otplib"); + const { authenticator } = await importRuntime("otplib"); const setting = await this.getSetting(userId); if (!setting.authenticator.enabled) { throw new Error("authenticator 未开启"); @@ -91,3 +89,6 @@ export class TwoFactorService { return true; } } + + + diff --git a/packages/ui/certd-server/src/modules/pipeline/service/getter/task-service-getter.ts b/packages/ui/certd-server/src/modules/pipeline/service/getter/task-service-getter.ts index 2f60acb82..a0e986317 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/getter/task-service-getter.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/getter/task-service-getter.ts @@ -1,4 +1,4 @@ -import { IServiceGetter } from "@certd/pipeline"; +import { IServiceGetter } from "@certd/pipeline"; import { ApplicationContext, IMidwayContainer, Provide, Scope, ScopeEnum } from "@midwayjs/core"; import { AccessGetter, AccessService } from "@certd/lib-server"; import { CnameProxyService } from "./cname-proxy-service.js"; @@ -13,7 +13,7 @@ import { CertInfoGetter } from "./cert-info-getter.js"; import { CertInfoService } from "../../../monitor/index.js"; import { ICertInfoGetter } from "@certd/plugin-lib"; import { CnameProviderService } from "../../../cname/service/cname-provider-service.js"; -import { RuntimeDepsService } from "../../../runtime-deps/runtime-deps-service.js"; + const serviceNames = ["ocrService"]; export class TaskServiceGetter implements IServiceGetter { @@ -39,8 +39,6 @@ export class TaskServiceGetter implements IServiceGetter { return (await this.getDomainVerifierGetter()) as T; } else if (serviceName === "certInfoGetter") { return (await this.getCertInfoGetter()) as T; - } else if (serviceName === "runtimeDepsService") { - return (await this.getRuntimeDepsService()) as T; } else { if (!serviceNames.includes(serviceName)) { throw new Error(`${serviceName} not in whitelist`); @@ -84,10 +82,6 @@ export class TaskServiceGetter implements IServiceGetter { const domainService: DomainService = await this.appCtx.getAsync("domainService"); return new DomainVerifierGetter(this.userId, this.projectId, domainService); } - - async getRuntimeDepsService(): Promise { - return await this.appCtx.getAsync("runtimeDepsService"); - } } @Provide() @Scope(ScopeEnum.Request, { allowDowngrade: true }) @@ -106,3 +100,5 @@ export type TaskServiceCreateReq = { userId: number; projectId?: number; }; + + diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts index 362eff8d8..8e3acabfe 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts @@ -962,7 +962,7 @@ export class PipelineService extends BaseService { return result; } - async batchDelete(ids: number[], userId?: number, projectId?: number):Promise { + async batchDelete(ids: number[], userId?: number, projectId?: number): Promise { if (!isPlus()) { throw new NeedVIPException("此功能需要升级Certd专业版"); } @@ -974,9 +974,9 @@ export class PipelineService extends BaseService { await this.checkUserId(id, projectId, "projectId"); } await this.delete(id); - ids.push(id); + ids.push(id); } - return ids.length + return ids.length; } async batchUpdateGroup(ids: number[], groupId: number, userId: any, projectId?: number) { diff --git a/packages/ui/certd-server/src/modules/plugin/service/plugin-service.ts b/packages/ui/certd-server/src/modules/plugin/service/plugin-service.ts index b7a2f4676..69905af0a 100644 --- a/packages/ui/certd-server/src/modules/plugin/service/plugin-service.ts +++ b/packages/ui/certd-server/src/modules/plugin/service/plugin-service.ts @@ -1,4 +1,4 @@ -import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core"; +import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core"; import { addonRegistry, BaseService, PageReq } from "@certd/lib-server"; import { PluginEntity } from "../entity/plugin.js"; import { InjectEntityModel } from "@midwayjs/typeorm"; @@ -6,14 +6,13 @@ import { IsNull, Not, 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"; -import { RuntimeDepsService } from "../../runtime-deps/runtime-deps-service.js"; +import { importRuntime as importRuntimeDirect, getRuntimeDepsService, pluginRegistry, accessRegistry, notificationRegistry } from "@certd/pipeline"; export type PluginImportReq = { content: string; @@ -49,10 +48,7 @@ export class PluginService extends BaseService { @Inject() builtInPluginService: BuiltInPluginService; - @Inject() - runtimeDepsService: RuntimeDepsService; - - //@ts-ignore +//@ts-ignore getRepository() { return this.repository; } @@ -270,7 +266,7 @@ export class PluginService extends BaseService { return; } await this.registerPlugin(item); - await this.runtimeDepsService.refreshPluginDeps(); + await this.refreshPluginDeps(); } async unRegisterById(id: any) { @@ -298,7 +294,12 @@ export class PluginService extends BaseService { } else { logger.warn(`不支持的插件类型:${item.pluginType}`); } - await this.runtimeDepsService.refreshPluginDeps(); + await this.refreshPluginDeps(); + } + + async refreshPluginDeps(){ + const service = getRuntimeDepsService(); + service.refreshPluginDeps(); } async update(param: any) { @@ -334,7 +335,7 @@ export class PluginService extends BaseService { if (!isBareModuleSpecifier(modulePath)) { return await importLocalModule(modulePath); } - return await this.runtimeDepsService.importRuntime(modulePath, logger); + return await importRuntimeDirect(modulePath, logger); } private async getPluginClassFromFile(item: any) { @@ -565,3 +566,5 @@ export class PluginService extends BaseService { } } } + + diff --git a/packages/ui/certd-server/src/modules/runtime-deps/npm-registry-resolver.ts b/packages/ui/certd-server/src/modules/runtime-deps/npm-registry-resolver.ts deleted file mode 100644 index 3247cfdad..000000000 --- a/packages/ui/certd-server/src/modules/runtime-deps/npm-registry-resolver.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Config, Provide, Scope, ScopeEnum } from "@midwayjs/core"; - -export type NpmRegistryResolverConfig = { - mode: "auto" | "fixed" | "system"; - fixedUrl: string; - candidates: string[]; - probeTimeoutMs: number; - cacheTtlMs: number; -}; - -export type RegistryProbeResult = { - registryUrl: string; - ok: boolean; - elapsedMs: number; -}; - -@Provide() -@Scope(ScopeEnum.Singleton) -export class NpmRegistryResolver { - @Config("runtimeDeps.registry") - config!: NpmRegistryResolverConfig; - - private cache?: { registryUrl: string; expiresAt: number }; - - async resolve(): Promise { - const config = this.config; - if (config?.mode === "fixed" && config.fixedUrl) { - return config.fixedUrl; - } - if (config?.mode === "system") { - return ""; - } - - const cached = this.cache; - if (cached && cached.expiresAt > Date.now()) { - return cached.registryUrl; - } - - const candidates = (config?.candidates || []).filter(Boolean); - const probes = await Promise.allSettled(candidates.map(registryUrl => this.probe(registryUrl))); - const okList = probes.map(item => (item.status === "fulfilled" ? item.value : null)).filter((item): item is RegistryProbeResult => !!item && item.ok); - - if (okList.length > 0) { - okList.sort((a, b) => a.elapsedMs - b.elapsedMs); - const best = okList[0].registryUrl; - this.cache = { - registryUrl: best, - expiresAt: Date.now() + (config?.cacheTtlMs || 0), - }; - return best; - } - - return ""; - } - - async probe(registryUrl: string): Promise { - const timeoutMs = this.config?.probeTimeoutMs || 3000; - const started = Date.now(); - try { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), timeoutMs); - try { - const res = await fetch(`${registryUrl.replace(/\/$/, "")}/-/ping`, { signal: controller.signal }); - return { - registryUrl, - ok: res.ok, - elapsedMs: Date.now() - started, - }; - } finally { - clearTimeout(timer); - } - } catch { - return { - registryUrl, - ok: false, - elapsedMs: Date.now() - started, - }; - } - } -} diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliyun-dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliyun-dns-provider.ts index 86d4c8589..df7cf0846 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliyun-dns-provider.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliyun-dns-provider.ts @@ -1,4 +1,4 @@ -import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, DnsResolveRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert"; +import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, DnsResolveRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert"; import { AliyunAccess } from "../../plugin-lib/aliyun/access/aliyun-access.js"; import { AliyunClient } from "../../plugin-lib/aliyun/index.js"; import { Pager, PageRes, PageSearch } from "@certd/pipeline"; @@ -16,7 +16,7 @@ export class AliyunDnsProvider extends AbstractDnsProvider { async onInstance() { const access: AliyunAccess = this.ctx.access as AliyunAccess; - this.client = new AliyunClient({ logger: this.logger, importRuntime: this.importRuntime.bind(this) }); + this.client = new AliyunClient({ logger: this.logger }); await this.client.init({ accessKeyId: access.accessKeyId, accessKeySecret: access.accessKeySecret, @@ -209,3 +209,4 @@ export class AliyunDnsProvider extends AbstractDnsProvider { } new AliyunDnsProvider(); + diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack/index.ts index 71167bf48..e022b0b49 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack/index.ts @@ -1,4 +1,4 @@ -import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { utils } from "@certd/basic"; import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; @@ -241,7 +241,7 @@ export class DeployCertToAliyunAckPlugin extends AbstractTaskPlugin { } async getClient(aliyunProvider: any, regionId: string) { - const client = new AliyunClient({ logger: this.logger, useROAClient: true, importRuntime: aliyunProvider.importRuntime.bind(aliyunProvider) }); + const client = new AliyunClient({ logger: this.logger, useROAClient: true }); await client.init({ accessKeyId: aliyunProvider.accessKeyId, accessKeySecret: aliyunProvider.accessKeySecret, @@ -275,3 +275,4 @@ export class DeployCertToAliyunAckPlugin extends AbstractTaskPlugin { } new DeployCertToAliyunAckPlugin(); + diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-alb/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-alb/index.ts index 04ab71ecc..06da96221 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-alb/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-alb/index.ts @@ -1,4 +1,4 @@ -import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; @@ -128,7 +128,7 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin { async onInstance() {} async getLBClient(access: AliyunAccess, region: string) { - const client = new AliyunClient({ logger: this.logger, importRuntime: access.importRuntime.bind(access) }); + const client = new AliyunClient({ logger: this.logger }); const version = "2020-06-16"; await client.init({ @@ -239,7 +239,7 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin { access: client.access, logger: this.logger, endpoint: this.casEndpoint, - }); + }); const certIds = []; for (const item of list) { @@ -295,7 +295,7 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin { access, logger: this.logger, endpoint: this.casEndpoint, - }); + }); if (certInfo.crt) { const certName = this.buildCertName(CertReader.getMainDomain(certInfo.crt)); @@ -404,3 +404,4 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin { } new AliyunDeployCertToALB(); + diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts index d4aef9121..a9cc28239 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts @@ -1,4 +1,4 @@ -import { optionsUtils } from "@certd/basic"; +import { optionsUtils } from "@certd/basic"; import { AbstractTaskPlugin, CertTargetItem, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline"; import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert"; import { CertInfo, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; @@ -136,7 +136,7 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin { access, logger: this.logger, endpoint: this.endpoint || "cas.aliyuncs.com", - }); + }); if (this.domainMatchMode === "auto") { const { result, deployedList } = await this.autoMatchedDeploy({ @@ -209,7 +209,7 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin { } async getClient(access: AliyunAccess) { - const client = new AliyunClient({ logger: this.logger, importRuntime: access.importRuntime.bind(access) }); + const client = new AliyunClient({ logger: this.logger }); await client.init({ accessKeyId: access.accessKeyId, accessKeySecret: access.accessKeySecret, @@ -293,3 +293,4 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin { } } new DeployCertToAliyunCDN(); + diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.ts index 0ee34e4d4..239a86795 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.ts @@ -1,4 +1,4 @@ -import { AbstractTaskPlugin, CertTargetItem, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline"; +import { AbstractTaskPlugin, CertTargetItem, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline"; import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; import dayjs from "dayjs"; import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; @@ -143,7 +143,7 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin { } async getClient(access: AliyunAccess) { - const client = new AliyunClient({ logger: this.logger, importRuntime: access.importRuntime.bind(access) }); + const client = new AliyunClient({ logger: this.logger }); await client.init({ accessKeyId: access.accessKeyId, accessKeySecret: access.accessKeySecret, @@ -224,3 +224,4 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin { } } new DeployCertToAliyunDCDN(); + diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts index 29caf9fed..9168b025c 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts @@ -137,8 +137,8 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin { const client = await this.getClient(access); - const $Util = await access.importRuntime("@alicloud/tea-util"); - const $OpenApi = await access.importRuntime("@alicloud/openapi-client"); + const $Util = await this.importRuntime("@alicloud/tea-util"); + const $OpenApi = await this.importRuntime("@alicloud/openapi-client"); let privateKey = this.cert.key; try { @@ -204,7 +204,7 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin { } async getClient(access: AliyunAccess) { - const $OpenApi = await access.importRuntime("@alicloud/openapi-client"); + const $OpenApi = await this.importRuntime("@alicloud/openapi-client"); const config = new $OpenApi.Config({ accessKeyId: access.accessKeyId, accessKeySecret: access.accessKeySecret, @@ -221,8 +221,8 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin { const access = await this.getAccess(this.accessId); const client = await this.getClient(access); - const $OpenApi = await access.importRuntime("@alicloud/openapi-client"); - const $Util = await access.importRuntime("@alicloud/tea-util"); + const $OpenApi = await this.importRuntime("@alicloud/openapi-client"); + const $Util = await this.importRuntime("@alicloud/tea-util"); const params = new $OpenApi.Params({ // 接口名称 action: "ListCustomDomains", diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts index 28f937f8e..12c32bd77 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts @@ -1,4 +1,4 @@ -import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertInfo, CertReader } from "@certd/plugin-cert"; import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; import { CertApplyPluginNames } from "@certd/plugin-cert"; @@ -117,7 +117,7 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin { async onInstance() {} async getLBClient(access: AliyunAccess, region: string) { - const client = new AliyunClient({ logger: this.logger, importRuntime: access.importRuntime.bind(access) }); + const client = new AliyunClient({ logger: this.logger }); const version = "2022-04-30"; await client.init({ @@ -221,7 +221,7 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin { access: client.access, logger: this.logger, endpoint: this.casEndpoint, - }); + }); const certIds = []; for (const item of list) { @@ -286,7 +286,7 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin { access, logger: this.logger, endpoint: this.casEndpoint, - }); + }); const certName = this.buildCertName(CertReader.getMainDomain(certInfo.crt)); @@ -389,3 +389,4 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin { } new AliyunDeployCertToNLB(); + diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts index 1691d02dc..02a09d8de 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts @@ -196,7 +196,7 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin { async getClient(access: AliyunAccess) { // @ts-ignore - const OSS = await access.importRuntime("ali-oss"); + const OSS = await this.importRuntime("ali-oss"); return new OSS.default({ accessKeyId: access.accessKeyId, accessKeySecret: access.accessKeySecret, diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts index 399a4f852..8b77ef60d 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts @@ -1,4 +1,4 @@ -import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertInfo } from "@certd/plugin-cert"; import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; import { CertApplyPluginNames } from "@certd/plugin-cert"; @@ -130,7 +130,7 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin { async onInstance() {} async getLBClient(access: AliyunAccess, region: string) { - const client = new AliyunClient({ logger: this.logger, importRuntime: access.importRuntime.bind(access) }); + const client = new AliyunClient({ logger: this.logger }); const version = "2014-05-15"; await client.init({ accessKeyId: access.accessKeyId, @@ -234,7 +234,7 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin { access, logger: this.logger, endpoint: this.casEndpoint, - }); + }); if (typeof this.cert === "object") { const name = this.appendTimeSuffix("certd"); @@ -412,3 +412,4 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin { } new AliyunDeployCertToSLB(); + diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/deploy-to-waf-cloud.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/deploy-to-waf-cloud.ts index 6320f5518..96db7b90e 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/deploy-to-waf-cloud.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/deploy-to-waf-cloud.ts @@ -1,4 +1,4 @@ -import { AbstractTaskPlugin, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { AbstractTaskPlugin, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; @@ -103,7 +103,7 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin { async onInstance() {} async getWafClient(access: AliyunAccess) { - const client = new AliyunClient({ logger: this.logger, importRuntime: access.importRuntime.bind(access) }); + const client = new AliyunClient({ logger: this.logger }); await client.init({ accessKeyId: access.accessKeyId, accessKeySecret: access.accessKeySecret, @@ -316,3 +316,4 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin { } new AliyunDeployCertToWafCloud(); + diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/deploy-to-waf-cname.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/deploy-to-waf-cname.ts index a655798de..bf7e07397 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/deploy-to-waf-cname.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/deploy-to-waf-cname.ts @@ -1,4 +1,4 @@ -import { AbstractTaskPlugin, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { AbstractTaskPlugin, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; @@ -115,7 +115,7 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin { async onInstance() {} async getWafClient(access: AliyunAccess) { - const client = new AliyunClient({ logger: this.logger, importRuntime: access.importRuntime.bind(access) }); + const client = new AliyunClient({ logger: this.logger }); await client.init({ accessKeyId: access.accessKeyId, accessKeySecret: access.accessKeySecret, @@ -144,7 +144,7 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin { access, logger: this.logger, endpoint: this.casEndpoint, - }); + }); const cert = this.cert as CertInfo; const casCert = this.cert as CasCertInfo; @@ -250,3 +250,4 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin { } new AliyunDeployCertToWaf(); + diff --git a/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/alioss-access.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/alioss-access.ts index 372325e78..c18d34ade 100644 --- a/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/alioss-access.ts +++ b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/alioss-access.ts @@ -101,7 +101,7 @@ export class AliossAccess extends BaseAccess { async getClient(access: AliyunAccess) { // @ts-ignore - const OSS = await access.importRuntime("ali-oss"); + const OSS = await this.importRuntime("ali-oss"); return new OSS.default({ accessKeyId: access.accessKeyId, accessKeySecret: access.accessKeySecret, diff --git a/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/aliyun-access.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/aliyun-access.ts index 090cb7dbd..727c01960 100644 --- a/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/aliyun-access.ts +++ b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/access/aliyun-access.ts @@ -1,4 +1,4 @@ -import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; +import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; import { AliyunClientV2 } from "../lib/aliyun-client-v2.js"; import { AliyunSslClient } from "../lib/ssl-client.js"; @IsAccess({ @@ -85,7 +85,7 @@ export class AliyunAccess extends BaseAccess { access: this, logger: this.ctx.logger, endpoint, - }); + }); return client; } @@ -94,8 +94,9 @@ export class AliyunAccess extends BaseAccess { access: this, logger: this.ctx.logger, endpoint: endpoint, - }); + }); } } new AliyunAccess(); + diff --git a/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/aliyun-client-v2.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/aliyun-client-v2.ts index 11e8a869f..5d08bc625 100644 --- a/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/aliyun-client-v2.ts +++ b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/aliyun-client-v2.ts @@ -1,5 +1,6 @@ -import { ILogger } from "@certd/basic"; +import { ILogger } from "@certd/basic"; import { AliyunAccess } from "../access/aliyun-access.js"; +import { importRuntime as importRuntimeDirect } from "@certd/pipeline"; export type AliyunClientV2Req = { action: string; @@ -19,18 +20,24 @@ export class AliyunClientV2 { logger: ILogger; endpoint: string; + client: any; constructor(opts: { access: AliyunAccess; logger: ILogger; endpoint: string }) { this.access = opts.access; this.logger = opts.logger; this.endpoint = opts.endpoint; + + } + + async importRuntime(name: string) { + return await importRuntimeDirect(name, this.logger); } async getClient() { if (this.client) { return this.client; } - const $OpenApi = await this.access.importRuntime("@alicloud/openapi-client"); + const $OpenApi = await this.importRuntime("@alicloud/openapi-client"); // const Credential = await import("@alicloud/credentials"); // //@ts-ignore // const credential = new Credential.default.default({ @@ -52,9 +59,9 @@ export class AliyunClientV2 { async doRequest(req: AliyunClientV2Req) { const client = await this.getClient(); - const $OpenApi = await this.access.importRuntime("@alicloud/openapi-client"); - const $Util = await this.access.importRuntime("@alicloud/tea-util"); - const OpenApiUtil = await this.access.importRuntime("@alicloud/openapi-util"); + const $OpenApi = await this.importRuntime("@alicloud/openapi-client"); + const $Util = await this.importRuntime("@alicloud/tea-util"); + const OpenApiUtil = await this.importRuntime("@alicloud/openapi-util"); const params = new $OpenApi.Params({ // 接口名称 action: req.action, @@ -89,3 +96,4 @@ export class AliyunClientV2 { return res?.body; } } + diff --git a/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/base-client.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/base-client.ts index b2950ddc4..83dd2f607 100644 --- a/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/base-client.ts +++ b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/base-client.ts @@ -1,21 +1,25 @@ -import { getGlobalAgents, ILogger } from "@certd/basic"; -import { ImportRuntime } from "@certd/pipeline"; +import { getGlobalAgents, ILogger } from "@certd/basic"; +import { importRuntime as importRuntimeDirect } from "@certd/pipeline"; export class AliyunClient { client: any; logger: ILogger; agent: any; useROAClient: boolean; - importRuntime: ImportRuntime; - constructor(opts: { logger: ILogger; useROAClient?: boolean; importRuntime?: ImportRuntime }) { + + constructor(opts: { logger: ILogger; useROAClient?: boolean }) { this.logger = opts.logger; this.useROAClient = opts.useROAClient || false; - this.importRuntime = opts.importRuntime || (async (specifier: string) => await import(specifier)); + const agents = getGlobalAgents(); this.agent = agents.httpsAgent; } + async importRuntime(specifier: string) { + return await importRuntimeDirect(specifier, this.logger); + } + async getSdk() { if (this.useROAClient) { return await this.getROAClient(); @@ -78,3 +82,4 @@ export class AliyunClient { return res; } } + diff --git a/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/ssl-client.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/ssl-client.ts index 4c26223ad..352de9bba 100644 --- a/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/ssl-client.ts +++ b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/ssl-client.ts @@ -1,4 +1,4 @@ -import { ILogger, utils } from "@certd/basic"; +import { ILogger, utils } from "@certd/basic"; import { AliyunAccess } from "../access/index.js"; import { AliyunClient } from "./index.js"; import { CertInfo, CertReader, SimpleCertDetail } from "@certd/plugin-lib"; @@ -12,6 +12,7 @@ export type AliyunSslClientOpts = { logger: ILogger; endpoint?: string; region?: string; + }; export type AliyunSslGetResourceListReq = { @@ -42,9 +43,11 @@ export type CasCertId = { export class AliyunSslClient { opts: AliyunSslClientOpts; logger: ILogger; + constructor(opts: AliyunSslClientOpts) { this.opts = opts; this.logger = opts.logger; + } checkRet(ret: any) { @@ -55,7 +58,7 @@ export class AliyunSslClient { async getClient() { const access = this.opts.access; - const client = new AliyunClient({ logger: this.opts.logger, importRuntime: access.importRuntime.bind(access) }); + const client = new AliyunClient({ logger: this.opts.logger }); let endpoint = this.opts.endpoint || "cas.aliyuncs.com"; if (this.opts.endpoint == null && this.opts.region) { @@ -251,3 +254,5 @@ export class AliyunSslClient { } } } + + diff --git a/packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/tencentcos.ts b/packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/tencentcos.ts index 5656f3e64..1f581d627 100644 --- a/packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/tencentcos.ts +++ b/packages/ui/certd-server/src/plugins/plugin-lib/oss/impls/tencentcos.ts @@ -1,4 +1,4 @@ -import dayjs from "dayjs"; +import dayjs from "dayjs"; import { TencentAccess, TencentCosAccess, TencentCosClient } from "../../tencent/index.js"; import { BaseOssClient, OssFileItem } from "../api.js"; @@ -19,7 +19,7 @@ export default class TencentOssClientImpl extends BaseOssClient { const key = this.join(this.rootDir, filePath); @@ -52,3 +52,4 @@ export default class TencentOssClientImpl extends BaseOssClient await import(specifier)); + + } + + async importRuntime(specifier: string) { + return await importRuntimeDirect(specifier, this.logger); } async getCosClient() { @@ -118,3 +122,4 @@ export class TencentCosClient { }); } } + diff --git a/packages/ui/certd-server/src/plugins/plugin-nginx-proxy-manager/NginxProxyManagerDeploy.yaml b/packages/ui/certd-server/src/plugins/plugin-nginx-proxy-manager/NginxProxyManagerDeploy.yaml deleted file mode 100644 index c01567fd7..000000000 --- a/packages/ui/certd-server/src/plugins/plugin-nginx-proxy-manager/NginxProxyManagerDeploy.yaml +++ /dev/null @@ -1,348 +0,0 @@ -name: NginxProxyManagerDeploy -icon: logos:nginx -title: Nginx Proxy Manager-部署到主机 -group: panel -desc: 上传自定义证书到 Nginx Proxy Manager,并绑定到所选主机。 -setting: null -sysSetting: null -type: custom -disabled: false -version: 1.0.0 -pluginType: deploy -author: samler -input: - cert: - title: 域名证书 - helper: 请选择前置任务产出的证书。 - component: - name: output-selector - from: - - ":cert:" - required: true - order: 0 - certDomains: - title: 证书域名 - component: - name: cert-domains-getter - mergeScript: | - return { - component: { - inputKey: ctx.compute(({ form }) => { - return form.cert; - }), - }, - } - required: false - order: 0 - accessId: - title: NPM授权 - component: - name: access-selector - type: samler/nginxProxyManager - helper: 选择用于部署的 Nginx Proxy Manager 授权。 - required: true - order: 0 - proxyHostIds: - title: 代理主机 - component: - name: remote-select - vModel: value - mode: tags - type: plugin - action: onGetProxyHostOptions - search: true - pager: false - multi: true - watches: - - certDomains - - accessId - required: true - helper: 选择要绑定此证书的一个或多个代理主机。 - mergeScript: | - return { - component: { - form: ctx.compute(({ form }) => { - return form; - }), - }, - } - order: 0 - certificateLabel: - title: 证书标识 - component: - name: a-input - allowClear: true - placeholder: certd_npm_example_com - helper: 可选。留空时默认使用 certd_npm_<主域名规范化>. - required: false - order: 0 - cleanupMatchingCertificates: - title: 自动清理未使用证书 - component: - name: a-switch - vModel: checked - helper: 部署成功后,自动删除除当前证书外所有未被任何主机引用的证书。 - required: false - order: 0 -output: {} -default: - strategy: - runStrategy: 1 -showRunStrategy: false -content: | - const { AbstractTaskPlugin } = await import("@certd/pipeline"); - const { CertReader } = await import("@certd/plugin-cert"); - - function normalizeDomain(domain) { - return String(domain ?? "").trim().toLowerCase(); - } - - function wildcardMatches(pattern, candidate) { - if (!pattern.startsWith("*.")) { - return false; - } - - const suffix = pattern.slice(1).toLowerCase(); - return candidate.endsWith(suffix); - } - - function isDomainMatch(left, right) { - const normalizedLeft = normalizeDomain(left); - const normalizedRight = normalizeDomain(right); - - return ( - normalizedLeft === normalizedRight || - wildcardMatches(normalizedLeft, normalizedRight) || - wildcardMatches(normalizedRight, normalizedLeft) - ); - } - - function normalizeDomainIdentity(domain) { - return normalizeDomain(domain).replace(/^\*\./, ""); - } - - function certificateHasBindings(certificate) { - return ( - (certificate.proxy_hosts?.length ?? 0) > 0 || - (certificate.redirection_hosts?.length ?? 0) > 0 || - (certificate.dead_hosts?.length ?? 0) > 0 || - (certificate.streams?.length ?? 0) > 0 - ); - } - - function sanitizeDomainSegment(value) { - const sanitized = String(value ?? "") - .trim() - .toLowerCase() - .replace(/[^a-z0-9]+/g, "_") - .replace(/^_+|_+$/g, "") - .replace(/_+/g, "_"); - - return sanitized || "unknown"; - } - - function buildDefaultCertificateLabel(cert) { - const mainDomain = CertReader.getMainDomain(cert.crt); - return `certd_npm_${sanitizeDomainSegment(mainDomain)}`; - } - - function normalizeStringList(input) { - if (Array.isArray(input)) { - return input; - } - - if (input == null || input === "") { - return []; - } - - return [input]; - } - - function resolveCertificateDomains(cert, configuredDomains) { - const configured = normalizeStringList(configuredDomains) - .map((value) => String(value).trim()) - .filter(Boolean); - - if (configured.length > 0) { - return Array.from(new Set(configured)); - } - - return new CertReader(cert).getAllDomains(); - } - - function buildProxyHostLabel(host) { - const domains = host.domain_names?.length ? host.domain_names.join(", ") : "(no domains)"; - return `${domains} <#${host.id}>`; - } - - function hasAnyCertDomainMatch(host, certDomains) { - if (!certDomains.length) { - return false; - } - - const hostDomains = host.domain_names ?? []; - return hostDomains.some((hostDomain) => certDomains.some((certDomain) => isDomainMatch(hostDomain, certDomain))); - } - - function buildProxyHostOptions(hosts, certDomains) { - const sortedHosts = [...hosts].sort((left, right) => { - return buildProxyHostLabel(left).localeCompare(buildProxyHostLabel(right)); - }); - - const matched = []; - const unmatched = []; - - for (const host of sortedHosts) { - const option = { - label: buildProxyHostLabel(host), - value: String(host.id), - domain: host.domain_names?.[0] ?? "", - }; - - if (hasAnyCertDomainMatch(host, certDomains)) { - matched.push(option); - } else { - unmatched.push(option); - } - } - - if (matched.length && unmatched.length) { - return [ - { - label: "匹配证书域名的主机", - options: matched, - }, - { - label: "其他代理主机", - options: unmatched, - }, - ]; - } - - return matched.length ? matched : unmatched; - } - - function normalizeProxyHostIds(proxyHostIds) { - return Array.from( - new Set( - normalizeStringList(proxyHostIds) - .map((value) => Number.parseInt(String(value), 10)) - .filter((value) => Number.isInteger(value) && value > 0), - ), - ); - } - - return class NpmDeployToProxyHosts extends AbstractTaskPlugin { - cert; - certDomains; - accessId; - proxyHostIds; - certificateLabel; - cleanupMatchingCertificates = false; - - async execute() { - const access = await this.getAccess(this.accessId); - const client = access.createClient(); - const proxyHostIds = normalizeProxyHostIds(this.proxyHostIds); - - if (proxyHostIds.length === 0) { - throw new Error("请至少选择一个 Nginx Proxy Manager 代理主机"); - } - - const certificateLabel = this.certificateLabel?.trim() || buildDefaultCertificateLabel(this.cert); - const certificateDomains = resolveCertificateDomains(this.cert, this.certDomains); - - let certificate = await client.findCustomCertificateByNiceName(certificateLabel); - if (!certificate) { - this.logger.info(`在 Nginx Proxy Manager 中创建自定义证书 "${certificateLabel}"`); - certificate = await client.createCustomCertificate(certificateLabel, certificateDomains); - } else { - this.logger.info(`复用已有自定义证书 "${certificateLabel}" (#${certificate.id})`); - } - - await client.uploadCertificate(certificate.id, { - certificate: this.cert.crt, - certificateKey: this.cert.key, - intermediateCertificate: this.cert.ic, - }); - this.logger.info(`证书内容已上传到 Nginx Proxy Manager 证书 #${certificate.id}`); - - for (const proxyHostId of proxyHostIds) { - this.logger.info(`将证书 #${certificate.id} 绑定到代理主机 #${proxyHostId}`); - try { - await client.assignCertificateToProxyHost(proxyHostId, certificate.id); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - throw new Error(`为代理主机 #${proxyHostId} 绑定证书失败:${message}`); - } - } - - if (this.cleanupMatchingCertificates === true) { - await this.cleanupOldCertificates(client, certificate.id); - } - - this.logger.info(`部署完成,共更新 ${proxyHostIds.length} 个代理主机`); - } - - async onGetProxyHostOptions(req = {}) { - if (!this.accessId) { - throw new Error("请先选择 Nginx Proxy Manager 授权"); - } - - const access = await this.getAccess(this.accessId); - const proxyHosts = await access.getProxyHostList(req); - return buildProxyHostOptions(proxyHosts, normalizeStringList(this.certDomains)); - } - - async cleanupOldCertificates(client, currentCertificateId) { - const certificates = await client.getCertificatesWithExpand(undefined, [ - "proxy_hosts", - "redirection_hosts", - "dead_hosts", - "streams", - ]); - - const candidates = certificates.filter((certificate) => { - return certificate.id !== currentCertificateId; - }); - - if (candidates.length === 0) { - this.logger.info("未发现可自动清理的旧证书"); - return; - } - - const deletedIds = []; - const skippedInUse = []; - const failedDeletes = []; - - for (const candidate of candidates) { - if (certificateHasBindings(candidate)) { - skippedInUse.push(`#${candidate.id} ${candidate.nice_name}`); - continue; - } - - this.logger.info(`自动清理旧证书 #${candidate.id} ${candidate.nice_name}`); - try { - await client.deleteCertificate(candidate.id); - deletedIds.push(candidate.id); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - failedDeletes.push(`#${candidate.id} ${candidate.nice_name}: ${message}`); - } - } - - if (deletedIds.length > 0) { - this.logger.info(`自动清理完成,共删除 ${deletedIds.length} 张旧证书:${deletedIds.map((id) => `#${id}`).join(", ")}`); - } else { - this.logger.info("未删除任何旧证书"); - } - - if (skippedInUse.length > 0) { - this.logger.info(`以下旧证书仍被其他资源引用,已跳过清理:${skippedInUse.join(", ")}`); - } - - if (failedDeletes.length > 0) { - this.logger.warn(`以下旧证书清理失败,已跳过:${failedDeletes.join(", ")}`); - } - } - }; diff --git a/packages/ui/certd-server/src/plugins/plugin-nginx-proxy-manager/nginxProxyManager.yaml b/packages/ui/certd-server/src/plugins/plugin-nginx-proxy-manager/nginxProxyManager.yaml deleted file mode 100644 index 59932d6df..000000000 --- a/packages/ui/certd-server/src/plugins/plugin-nginx-proxy-manager/nginxProxyManager.yaml +++ /dev/null @@ -1,347 +0,0 @@ -name: nginxProxyManager -icon: logos:nginx -title: Nginx Proxy Manager 授权 -group: null -desc: 用于登录 Nginx Proxy Manager,并为代理主机证书部署提供授权。 -setting: null -sysSetting: null -type: custom -disabled: false -version: 1.0.0 -pluginType: access -author: samler -input: - endpoint: - title: NPM 地址 - component: - name: a-input - allowClear: true - placeholder: https://npm.example.com - helper: 请输入 Nginx Proxy Manager 根地址,不要带 /api 后缀。 - required: true - email: - title: 邮箱 - component: - name: a-input - allowClear: true - placeholder: admin@example.com - required: true - password: - title: 密码 - component: - name: a-input-password - allowClear: true - placeholder: 请输入密码 - required: true - encrypt: true - totpSecret: - title: TOTP 密钥 - component: - name: a-input-password - allowClear: true - placeholder: Optional base32 TOTP secret - helper: 当 Nginx Proxy Manager 账号开启 2FA 时必填。 - required: false - encrypt: true - ignoreTls: - title: 忽略无效 TLS - component: - name: a-switch - vModel: checked - helper: 仅在 Nginx Proxy Manager 使用自签 HTTPS 证书时开启。 - required: false - testRequest: - title: 测试 - component: - name: api-test - action: TestRequest - helper: 测试登录并拉取代理主机列表。 -content: | - const runtimeImport = import; - const { BaseAccess } = await runtimeImport("@certd/pipeline"); - const httpsModule = await runtimeImport("node:https"); - const { URL } = await runtimeImport("node:url"); - const axiosModule = await runtimeImport("axios"); - const formDataModule = await runtimeImport("form-data"); - - const https = httpsModule.default ?? httpsModule; - const axios = axiosModule.default ?? axiosModule; - const FormData = formDataModule.default ?? formDataModule; - - function normalizeEndpoint(endpoint) { - const trimmed = String(endpoint ?? "").trim(); - if (!trimmed) { - throw new Error("Nginx Proxy Manager 地址不能为空"); - } - - const withoutTrailingSlash = trimmed.replace(/\/+$/, ""); - return withoutTrailingSlash.endsWith("/api") - ? withoutTrailingSlash.slice(0, -4) - : withoutTrailingSlash; - } - - function buildHttpsAgent(endpoint, ignoreTls) { - const url = new URL(endpoint); - if (url.protocol !== "https:") { - return undefined; - } - - return new https.Agent({ - rejectUnauthorized: !ignoreTls, - }); - } - - function describeError(error, action) { - if (axios.isAxiosError(error)) { - const status = error.response?.status; - const data = error.response?.data; - const message = - data?.error?.message || - data?.error || - data?.message || - (typeof data === "string" ? data : null) || - error.message; - return new Error(`${action} failed${status ? ` (${status})` : ""}: ${message}`); - } - - if (error instanceof Error) { - return new Error(`${action} failed: ${error.message}`); - } - - return new Error(`${action} failed`); - } - - class NginxProxyManagerClient { - constructor(options) { - this.options = options; - this.importFn = options.importFn; - this.endpoint = normalizeEndpoint(options.endpoint); - this.apiBaseUrl = `${this.endpoint}/api`; - this.token = undefined; - this.tokenPromise = undefined; - this.httpClient = axios.create({ - baseURL: this.apiBaseUrl, - timeout: 30000, - maxBodyLength: Infinity, - maxContentLength: Infinity, - httpsAgent: buildHttpsAgent(this.endpoint, options.ignoreTls === true), - headers: { - Accept: "application/json", - }, - }); - } - - async verifyAccess() { - const proxyHosts = await this.getProxyHosts(); - return { - proxyHostCount: proxyHosts.length, - }; - } - - async getProxyHosts(searchQuery) { - return await this.requestWithAuth({ - method: "GET", - url: "/nginx/proxy-hosts", - params: { - expand: "certificate", - ...(searchQuery ? { query: searchQuery } : {}), - }, - }); - } - - async getCertificates(searchQuery) { - return await this.requestWithAuth({ - method: "GET", - url: "/nginx/certificates", - params: searchQuery ? { query: searchQuery } : undefined, - }); - } - - async getCertificatesWithExpand(searchQuery, expand = []) { - return await this.requestWithAuth({ - method: "GET", - url: "/nginx/certificates", - params: { - ...(searchQuery ? { query: searchQuery } : {}), - ...(expand.length > 0 ? { expand: expand.join(",") } : {}), - }, - }); - } - - async findCustomCertificateByNiceName(niceName) { - const certificates = await this.getCertificates(niceName); - return certificates.find((certificate) => { - return certificate.provider === "other" && certificate.nice_name === niceName; - }); - } - - async createCustomCertificate(niceName, domainNames = []) { - return await this.requestWithAuth({ - method: "POST", - url: "/nginx/certificates", - data: { - provider: "other", - nice_name: niceName, - domain_names: domainNames, - }, - }); - } - - async deleteCertificate(certificateId) { - await this.requestWithAuth({ - method: "DELETE", - url: `/nginx/certificates/${certificateId}`, - }); - } - - async uploadCertificate(certificateId, payload) { - const form = new FormData(); - form.append("certificate", Buffer.from(payload.certificate, "utf8"), { - filename: "fullchain.pem", - contentType: "application/x-pem-file", - }); - form.append("certificate_key", Buffer.from(payload.certificateKey, "utf8"), { - filename: "privkey.pem", - contentType: "application/x-pem-file", - }); - if (payload.intermediateCertificate) { - form.append("intermediate_certificate", Buffer.from(payload.intermediateCertificate, "utf8"), { - filename: "chain.pem", - contentType: "application/x-pem-file", - }); - } - - await this.requestWithAuth({ - method: "POST", - url: `/nginx/certificates/${certificateId}/upload`, - data: form, - headers: form.getHeaders(), - }); - } - - async assignCertificateToProxyHost(hostId, certificateId) { - await this.requestWithAuth({ - method: "PUT", - url: `/nginx/proxy-hosts/${hostId}`, - data: { - certificate_id: certificateId, - }, - }); - } - - async login() { - if (this.token) { - return this.token; - } - - if (!this.tokenPromise) { - this.tokenPromise = this.performLogin().finally(() => { - this.tokenPromise = undefined; - }); - } - - this.token = await this.tokenPromise; - return this.token; - } - - async performLogin() { - const initialLogin = await this.request({ - method: "POST", - url: "/tokens", - data: { - identity: this.options.email, - secret: this.options.password, - }, - }); - - if (initialLogin.token) { - return initialLogin.token; - } - - if (!initialLogin.requires_2fa || !initialLogin.challenge_token) { - throw new Error("登录失败:Nginx Proxy Manager 未返回访问令牌"); - } - - if (!this.options.totpSecret) { - throw new Error("登录失败:该 Nginx Proxy Manager 账号启用了 2FA,但未配置 totpSecret"); - } - - let code; - try { - const { authenticator } = await this.importFn("otplib"); - code = authenticator.generate(this.options.totpSecret); - } catch (error) { - throw describeError(error, "Generating TOTP code"); - } - - const completedLogin = await this.request({ - method: "POST", - url: "/tokens/2fa", - data: { - challenge_token: initialLogin.challenge_token, - code, - }, - }); - - if (!completedLogin.token) { - throw new Error("2FA 登录失败:Nginx Proxy Manager 未返回访问令牌"); - } - - return completedLogin.token; - } - - async requestWithAuth(config) { - const token = await this.login(); - const headers = { - ...(config.headers ?? {}), - Authorization: `Bearer ${token}`, - }; - - return await this.request({ - ...config, - headers, - }); - } - - async request(config) { - const action = `${config.method ?? "GET"} ${config.url ?? "/"}`; - try { - const response = await this.httpClient.request(config); - return response.data; - } catch (error) { - throw describeError(error, action); - } - } - } - - return class NginxProxyManagerAccess extends BaseAccess { - endpoint = ""; - email = ""; - password = ""; - totpSecret = ""; - ignoreTls = false; - testRequest = true; - - createClient() { - return new NginxProxyManagerClient({ - endpoint: this.endpoint, - email: this.email, - password: this.password, - totpSecret: this.totpSecret || undefined, - ignoreTls: this.ignoreTls === true, - importFn: this.importRuntime.bind(this), - }); - } - - async onTestRequest() { - const client = this.createClient(); - const result = await client.verifyAccess(); - this.ctx.logger.info(`Nginx Proxy Manager 授权验证成功,找到 ${result.proxyHostCount} 个代理主机`); - return `成功(${result.proxyHostCount} 个代理主机)`; - } - - async getProxyHostList(req = {}) { - const client = this.createClient(); - return await client.getProxyHosts(req.searchKey); - } - }; diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cos/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cos/index.ts index ab84df6f2..d48fb58cd 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cos/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cos/index.ts @@ -142,7 +142,7 @@ export class DeployCertToTencentCosPlugin extends AbstractTaskPlugin { async onGetDomainList(data: any) { const access = await this.getAccess(this.accessId); - const cosv5 = await (this as any).importRuntime("cos-nodejs-sdk-v5"); + const cosv5 = await this.importRuntime("cos-nodejs-sdk-v5"); const cos = new cosv5.default({ SecretId: access.secretId, SecretKey: access.secretKey, diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-live/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-live/index.ts index 4d11fc8c2..53116a070 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-live/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-live/index.ts @@ -95,7 +95,7 @@ export class TencentDeployCertToLive extends AbstractTaskPlugin { async getLiveClient() { const accessProvider = await this.getAccess(this.accessId); - const sdk = await (this as any).importRuntime("tencentcloud-sdk-nodejs/tencentcloud/services/live/v20180801/index.js"); + const sdk = await this.importRuntime("tencentcloud-sdk-nodejs/tencentcloud/services/live/v20180801/index.js"); const CssClient = sdk.v20180801.Client; const clientConfig = { diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/upload-to-tencent/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/upload-to-tencent/index.ts index 03b25bc1a..314bcf573 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/upload-to-tencent/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/upload-to-tencent/index.ts @@ -53,7 +53,7 @@ export class UploadCertToTencent extends AbstractTaskPlugin { Client: any; async onInstance() { - const sdk = await (this as any).importRuntime("tencentcloud-sdk-nodejs/tencentcloud/services/ssl/v20191205/index.js"); + const sdk = await this.importRuntime("tencentcloud-sdk-nodejs/tencentcloud/services/ssl/v20191205/index.js"); this.Client = sdk.v20191205.Client; } diff --git a/packages/ui/certd-server/src/plugins/plugin-volcengine/cdn-client.ts b/packages/ui/certd-server/src/plugins/plugin-volcengine/cdn-client.ts index 3678ede0e..14740e89f 100644 --- a/packages/ui/certd-server/src/plugins/plugin-volcengine/cdn-client.ts +++ b/packages/ui/certd-server/src/plugins/plugin-volcengine/cdn-client.ts @@ -10,12 +10,15 @@ export class VolcengineCdnClient { this.opts = opts; } + async importRuntime(packageName: string) { + return this.opts.access.importRuntime(packageName); + } + async getCdnClient() { if (this.service) { return this.service; } - const importRuntime = this.opts.importRuntime || this.opts.access.importRuntime.bind(this.opts.access); - const { cdn } = await importRuntime("@volcengine/openapi"); + const { cdn } = await this.importRuntime("@volcengine/openapi"); const service = new cdn.CdnService(); // 设置ak、sk service.setAccessKeyId(this.opts.access.accessKeyId); diff --git a/packages/ui/certd-server/src/plugins/plugin-volcengine/dns-client.ts b/packages/ui/certd-server/src/plugins/plugin-volcengine/dns-client.ts index 68cd49738..8f81c1a0a 100644 --- a/packages/ui/certd-server/src/plugins/plugin-volcengine/dns-client.ts +++ b/packages/ui/certd-server/src/plugins/plugin-volcengine/dns-client.ts @@ -19,10 +19,12 @@ export class VolcengineDnsClient { constructor(opts: VolcengineOpts) { this.opts = opts; } + async importRuntime(packageName: string) { + return this.opts.access.importRuntime(packageName); + } async doRequest(req: VolcengineReq) { - const importRuntime = this.opts.importRuntime || this.opts.access.importRuntime.bind(this.opts.access); - const { Signer } = await importRuntime("@volcengine/openapi"); + const { Signer } = await this.importRuntime("@volcengine/openapi"); // http request data const openApiRequestData: any = { diff --git a/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-vod.ts b/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-vod.ts index 31bbc6be4..4fb474856 100644 --- a/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-vod.ts +++ b/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-vod.ts @@ -243,7 +243,7 @@ export class VolcengineDeployToVOD extends AbstractTaskPlugin { return { value: item.Domain, label: item.Domain, - domain : item.Domain, + domain: item.Domain, }; }); return this.ctx.utils.options.buildGroupOptions(list, this.certDomains); diff --git a/packages/ui/certd-server/src/plugins/plugin-volcengine/ve-client.ts b/packages/ui/certd-server/src/plugins/plugin-volcengine/ve-client.ts index 2bd2725ff..48f3b7490 100644 --- a/packages/ui/certd-server/src/plugins/plugin-volcengine/ve-client.ts +++ b/packages/ui/certd-server/src/plugins/plugin-volcengine/ve-client.ts @@ -1,12 +1,10 @@ import { VolcengineAccess } from "./access.js"; import { HttpClient, ILogger } from "@certd/basic"; -import { ImportRuntime } from "@certd/pipeline"; export type VolcengineOpts = { access: VolcengineAccess; logger: ILogger; http: HttpClient; - importRuntime?: ImportRuntime; }; export class VolcengineClient { @@ -17,6 +15,10 @@ export class VolcengineClient { this.opts = opts; } + async importRuntime(packageName: string) { + return this.opts.access.importRuntime(packageName); + } + async getCertCenterService() { const CommonService = await this.getServiceCls(); @@ -142,8 +144,7 @@ export class VolcengineClient { } async getTOSService(opts: { region?: string }) { - const importRuntime = this.opts.importRuntime || this.opts.access.importRuntime.bind(this.opts.access); - const { TosClient } = await importRuntime("@volcengine/tos-sdk"); + const { TosClient } = await this.importRuntime("@volcengine/tos-sdk"); const client = new TosClient({ accessKeyId: this.opts.access.accessKeyId, @@ -172,8 +173,7 @@ export class VolcengineClient { if (this.CommonService) { return this.CommonService; } - const importRuntime = this.opts.importRuntime || this.opts.access.importRuntime.bind(this.opts.access); - const { Service } = await importRuntime("@volcengine/openapi"); + const { Service } = await this.importRuntime("@volcengine/openapi"); class CommonService extends Service { Generic: any; diff --git a/packages/ui/certd-server/src/plugins/plugin-volcengine/volcengine-dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-volcengine/volcengine-dns-provider.ts index 762a74ae8..3bf5b35ee 100644 --- a/packages/ui/certd-server/src/plugins/plugin-volcengine/volcengine-dns-provider.ts +++ b/packages/ui/certd-server/src/plugins/plugin-volcengine/volcengine-dns-provider.ts @@ -22,7 +22,6 @@ export class VolcengineDnsProvider extends AbstractDnsProvider { access: this.access, logger: this.logger, http: this.http, - importRuntime: this.importRuntime.bind(this), }); }