mirror of
https://github.com/certd/certd.git
synced 2026-07-15 02:07:38 +08:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cc74a1c0a | |||
| 167b303fae | |||
| b91c9e4ea6 | |||
| 584b7b6f45 | |||
| 2312c444ef | |||
| 0373d019f8 | |||
| fe09e75b80 | |||
| 432c9c6cc1 | |||
| 5fc1d55879 |
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复火山引擎查不到自定义源站域名的问题 ([02dabe1](https://github.com/certd/certd/commit/02dabe11db3e9b13ca4621ce9ddd2b808bfca390))
|
||||||
|
* 修复火山引擎自定义源站域名查询不到的问题 ([e44bf9d](https://github.com/certd/certd/commit/e44bf9d77375d48ac7fd1582e69fae02dfd248fa))
|
||||||
|
* **pipeline:** 重构运行时依赖加载逻辑,修复火山引擎DNS解析报runtimeDepsService未初始化的bug ([ec69b8f](https://github.com/certd/certd/commit/ec69b8f11bfd4b20991aef74a72a47182ca79a9d))
|
||||||
|
|
||||||
## [1.42.3](https://github.com/certd/certd/compare/v1.42.2...v1.42.3) (2026-07-08)
|
## [1.42.3](https://github.com/certd/certd/compare/v1.42.2...v1.42.3) (2026-07-08)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"test:unit": "cross-env NODE_ENV=unittest pnpm -r --workspace-concurrency=1 run test:unit",
|
"test:unit": "cross-env NODE_ENV=unittest pnpm -r --workspace-concurrency=1 run test:unit",
|
||||||
"pub": "echo 1",
|
"pub": "echo 1",
|
||||||
"dev": "pnpm run -r --parallel compile ",
|
"dev": "pnpm run -r --parallel compile ",
|
||||||
|
"lint_all": "pnpm run -r --parallel lint ",
|
||||||
"pub_all": "node ./scripts/pub-all.js",
|
"pub_all": "node ./scripts/pub-all.js",
|
||||||
"release": "time /t >trigger/release.trigger && git add trigger/release.trigger && git commit -m \"build: release\" && git push",
|
"release": "time /t >trigger/release.trigger && git add trigger/release.trigger && git commit -m \"build: release\" && git push",
|
||||||
"publish_to_atomgit": "node --experimental-json-modules ./scripts/publish-atomgit.js",
|
"publish_to_atomgit": "node --experimental-json-modules ./scripts/publish-atomgit.js",
|
||||||
|
|||||||
@@ -75,5 +75,5 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||||
},
|
},
|
||||||
"gitHead": "a6ef6996c3a68ce0c2a4577a0934ed74a53f0515"
|
"gitHead": "268cd6cc9cb4f1f3d5d5d77859a82f18f0cb6db7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,5 +54,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "a6ef6996c3a68ce0c2a4577a0934ed74a53f0515"
|
"gitHead": "268cd6cc9cb4f1f3d5d5d77859a82f18f0cb6db7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,5 +51,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "a6ef6996c3a68ce0c2a4577a0934ed74a53f0515"
|
"gitHead": "268cd6cc9cb4f1f3d5d5d77859a82f18f0cb6db7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { FormItemProps } from "../dt/index.js";
|
|||||||
import { HttpClient, ILogger, utils } from "@certd/basic";
|
import { HttpClient, ILogger, utils } from "@certd/basic";
|
||||||
import * as _ from "lodash-es";
|
import * as _ from "lodash-es";
|
||||||
import { PluginRequestHandleReq } from "../plugin/index.js";
|
import { PluginRequestHandleReq } from "../plugin/index.js";
|
||||||
import { IRuntimeDepsService, IServiceGetter } from "../service/index.js";
|
import { IServiceGetter, getRuntimeDepsService } from "../service/index.js";
|
||||||
|
|
||||||
// export type AccessRequestHandleReqInput<T = any> = {
|
// export type AccessRequestHandleReqInput<T = any> = {
|
||||||
// id?: number;
|
// id?: number;
|
||||||
@@ -48,20 +48,13 @@ export type AccessContext = {
|
|||||||
|
|
||||||
export abstract class BaseAccess implements IAccess {
|
export abstract class BaseAccess implements IAccess {
|
||||||
ctx!: AccessContext;
|
ctx!: AccessContext;
|
||||||
runtimeDepsService?: IRuntimeDepsService;
|
|
||||||
|
|
||||||
async importRuntime(specifier: string) {
|
async importRuntime(specifier: string) {
|
||||||
if (!this.runtimeDepsService) {
|
return await getRuntimeDepsService().importRuntime(specifier, this.ctx.logger);
|
||||||
throw new Error("runtimeDepsService 未初始化");
|
|
||||||
}
|
|
||||||
return await this.runtimeDepsService.importRuntime(specifier, this.ctx.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setCtx(ctx: AccessContext) {
|
async setCtx(ctx: AccessContext) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
if (!this.runtimeDepsService && this.ctx.serviceGetter) {
|
|
||||||
this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onRequest(req: AccessRequestHandleReq) {
|
async onRequest(req: AccessRequestHandleReq) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Registrable } from "../registry/index.js";
|
|||||||
import { FormItemProps, HistoryResult, Pipeline } from "../dt/index.js";
|
import { FormItemProps, HistoryResult, Pipeline } from "../dt/index.js";
|
||||||
import { HttpClient, ILogger, utils } from "@certd/basic";
|
import { HttpClient, ILogger, utils } from "@certd/basic";
|
||||||
import * as _ from "lodash-es";
|
import * as _ from "lodash-es";
|
||||||
import { IEmailService, IRuntimeDepsService, IServiceGetter } from "../service/index.js";
|
import { IEmailService, IServiceGetter, getRuntimeDepsService } from "../service/index.js";
|
||||||
|
|
||||||
export type NotificationBody = {
|
export type NotificationBody = {
|
||||||
userId?: number;
|
userId?: number;
|
||||||
@@ -89,13 +89,9 @@ export abstract class BaseNotification implements INotification {
|
|||||||
ctx!: NotificationContext;
|
ctx!: NotificationContext;
|
||||||
http!: HttpClient;
|
http!: HttpClient;
|
||||||
logger!: ILogger;
|
logger!: ILogger;
|
||||||
runtimeDepsService?: IRuntimeDepsService;
|
|
||||||
|
|
||||||
async importRuntime(specifier: string) {
|
async importRuntime(specifier: string) {
|
||||||
if (!this.runtimeDepsService) {
|
return await getRuntimeDepsService().importRuntime(specifier, this.logger);
|
||||||
return await import(specifier);
|
|
||||||
}
|
|
||||||
return await this.runtimeDepsService.importRuntime(specifier, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async doSend(body: NotificationBody) {
|
async doSend(body: NotificationBody) {
|
||||||
@@ -109,9 +105,6 @@ export abstract class BaseNotification implements INotification {
|
|||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.http = ctx.http;
|
this.http = ctx.http;
|
||||||
this.logger = ctx.logger;
|
this.logger = ctx.logger;
|
||||||
if (!this.runtimeDepsService && this.ctx.serviceGetter) {
|
|
||||||
this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setDefine = (define: NotificationDefine) => {
|
setDefine = (define: NotificationDefine) => {
|
||||||
this.define = define;
|
this.define = define;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { INotificationService } from "../notification/index.js";
|
|||||||
import { Registrable } from "../registry/index.js";
|
import { Registrable } from "../registry/index.js";
|
||||||
import { IPluginConfigService } from "../service/config.js";
|
import { IPluginConfigService } from "../service/config.js";
|
||||||
import { TaskEmitter } from "../service/emit.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<T = any> = {
|
export type PluginRequestHandleReq<T = any> = {
|
||||||
typeName: string;
|
typeName: string;
|
||||||
@@ -76,7 +76,7 @@ export type ITaskPlugin = {
|
|||||||
execute(): Promise<void | string>;
|
execute(): Promise<void | string>;
|
||||||
onRequest(req: PluginRequestHandleReq<any>): Promise<any>;
|
onRequest(req: PluginRequestHandleReq<any>): Promise<any>;
|
||||||
setCtx(ctx: TaskInstanceContext): Promise<void>;
|
setCtx(ctx: TaskInstanceContext): Promise<void>;
|
||||||
importRuntime?(specifier: string): Promise<any>;
|
importRuntime(specifier: string): Promise<any>;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,13 +150,9 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
|||||||
logger!: ILogger;
|
logger!: ILogger;
|
||||||
http!: HttpClient;
|
http!: HttpClient;
|
||||||
accessService!: IAccessService;
|
accessService!: IAccessService;
|
||||||
runtimeDepsService!: IRuntimeDepsService;
|
|
||||||
|
|
||||||
async importRuntime(specifier: string) {
|
async importRuntime(specifier: string) {
|
||||||
if (!this.runtimeDepsService) {
|
return await getRuntimeDepsService().importRuntime(specifier, this.logger);
|
||||||
throw new Error("runtimeDepsService 未初始化");
|
|
||||||
}
|
|
||||||
return await this.runtimeDepsService.importRuntime(specifier, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearLastStatus() {
|
clearLastStatus() {
|
||||||
@@ -178,9 +174,6 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
|||||||
this.logger = ctx.logger;
|
this.logger = ctx.logger;
|
||||||
this.accessService = ctx.accessService;
|
this.accessService = ctx.accessService;
|
||||||
this.http = ctx.http;
|
this.http = ctx.http;
|
||||||
if (!this.runtimeDepsService && this.ctx.serviceGetter) {
|
|
||||||
this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService");
|
|
||||||
}
|
|
||||||
// 将证书加入secret
|
// 将证书加入secret
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (this.cert && this.cert.crt && this.cert.key) {
|
if (this.cert && this.cert.crt && this.cert.key) {
|
||||||
|
|||||||
+8
-18
@@ -1,41 +1,31 @@
|
|||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import { NpmRegistryResolver } from "./npm-registry-resolver.js";
|
import { NpmRegistryResolver } from "./runtime-deps-service.js";
|
||||||
|
|
||||||
describe("NpmRegistryResolver", () => {
|
describe("NpmRegistryResolver", () => {
|
||||||
it("chooses the fastest successful registry in auto mode", async () => {
|
it("chooses the fastest successful registry in auto mode", async () => {
|
||||||
const resolver = new NpmRegistryResolver();
|
const resolver = new NpmRegistryResolver({
|
||||||
resolver.config = {
|
|
||||||
mode: "auto",
|
mode: "auto",
|
||||||
fixedUrl: "",
|
|
||||||
candidates: ["https://slow.example.com", "https://fast.example.com"],
|
candidates: ["https://slow.example.com", "https://fast.example.com"],
|
||||||
probeTimeoutMs: 100,
|
probeTimeoutMs: 100,
|
||||||
cacheTtlMs: 1000,
|
cacheTtlMs: 1000,
|
||||||
};
|
});
|
||||||
resolver.probe = async registryUrl => {
|
resolver.probe = async registryUrl => ({
|
||||||
return {
|
|
||||||
registryUrl,
|
registryUrl,
|
||||||
ok: true,
|
ok: true,
|
||||||
elapsedMs: registryUrl.includes("fast") ? 10 : 50,
|
elapsedMs: registryUrl.includes("fast") ? 10 : 50,
|
||||||
};
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const result = await resolver.resolve();
|
const result = await resolver.resolve();
|
||||||
|
|
||||||
assert.equal(result, "https://fast.example.com");
|
assert.equal(result, "https://fast.example.com");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses fixed registry without probing", async () => {
|
it("uses fixed registry without probing", async () => {
|
||||||
const resolver = new NpmRegistryResolver();
|
const resolver = new NpmRegistryResolver({
|
||||||
resolver.config = {
|
|
||||||
mode: "fixed",
|
mode: "fixed",
|
||||||
fixedUrl: "https://registry.example.com",
|
fixedUrl: "https://registry.example.com",
|
||||||
candidates: [],
|
|
||||||
probeTimeoutMs: 100,
|
probeTimeoutMs: 100,
|
||||||
cacheTtlMs: 1000,
|
cacheTtlMs: 1000,
|
||||||
};
|
});
|
||||||
|
|
||||||
const result = await resolver.resolve();
|
const result = await resolver.resolve();
|
||||||
|
|
||||||
assert.equal(result, "https://registry.example.com");
|
assert.equal(result, "https://registry.example.com");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
+40
-288
@@ -1,10 +1,10 @@
|
|||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import { RuntimeDepsService, type RuntimeDependencyPluginDefine } from "./runtime-deps-service.js";
|
import { RuntimeDepsService, type RuntimeDependencyPluginDefine } from "./runtime-deps-service.js";
|
||||||
import { accessRegistry, pluginRegistry } from "@certd/pipeline";
|
import { accessRegistry } from "../access/registry.js";
|
||||||
import { addonRegistry } from "@certd/lib-server";
|
import { pluginRegistry } from "../plugin/registry.js";
|
||||||
|
|
||||||
describe("RuntimeDepsService", () => {
|
describe("RuntimeDepsService", () => {
|
||||||
it("detects conflicting dependency ranges across plugins", () => {
|
it("detects conflicting dependency ranges across plugins", () => {
|
||||||
@@ -13,7 +13,6 @@ describe("RuntimeDepsService", () => {
|
|||||||
{ name: "a", dependPackages: { foo: "^1.0.0" } },
|
{ name: "a", dependPackages: { foo: "^1.0.0" } },
|
||||||
{ name: "b", dependPackages: { foo: "^1.2.0" } },
|
{ name: "b", dependPackages: { foo: "^1.2.0" } },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert.deepEqual(merged.dependencies, { foo: "^1.0.0" });
|
assert.deepEqual(merged.dependencies, { foo: "^1.0.0" });
|
||||||
assert.equal(merged.conflicts.length, 0);
|
assert.equal(merged.conflicts.length, 0);
|
||||||
});
|
});
|
||||||
@@ -24,62 +23,45 @@ describe("RuntimeDepsService", () => {
|
|||||||
{ name: "a", dependPackages: { foo: "^1.0.0" } },
|
{ name: "a", dependPackages: { foo: "^1.0.0" } },
|
||||||
{ name: "b", dependPackages: { foo: "^2.0.0" } },
|
{ name: "b", dependPackages: { foo: "^2.0.0" } },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert.equal(merged.conflicts.length, 1);
|
assert.equal(merged.conflicts.length, 1);
|
||||||
assert.equal(merged.conflicts[0].packageName, "foo");
|
assert.equal(merged.conflicts[0].packageName, "foo");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("builds a runtime package manifest in the target directory", async () => {
|
it("builds a runtime package manifest in the target directory", async () => {
|
||||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-"));
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-"));
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({ rootDir });
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
service.registryResolver = {
|
service.registryResolver = {
|
||||||
async resolve() {
|
async resolve() { return "https://registry.npmmirror.com"; },
|
||||||
return "https://registry.npmmirror.com";
|
|
||||||
},
|
|
||||||
} as any;
|
} as any;
|
||||||
service.commandRunner = {
|
service.commandRunner = {
|
||||||
async run(command: string, args: string[]) {
|
async run(command: string, args: string[]) {
|
||||||
assert.equal(command, "pnpm");
|
assert.equal(command, "pnpm");
|
||||||
if (args.includes("--version")) {
|
if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; }
|
||||||
return { stdout: "9.1.0\n", stderr: "", code: 0 };
|
|
||||||
}
|
|
||||||
assert.equal(args[0], "install");
|
assert.equal(args[0], "install");
|
||||||
assert.ok(args.includes("--ignore-workspace"));
|
assert.ok(args.includes("--ignore-workspace"));
|
||||||
assert.ok(args.includes("--no-frozen-lockfile"));
|
assert.ok(args.includes("--no-frozen-lockfile"));
|
||||||
return { stdout: "", stderr: "", code: 0 };
|
return { stdout: "", stderr: "", code: 0 };
|
||||||
},
|
},
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
const plugins: RuntimeDependencyPluginDefine[] = [{ name: "a", dependPackages: { foo: "^1.0.0" } }];
|
const plugins: RuntimeDependencyPluginDefine[] = [{ name: "a", dependPackages: { foo: "^1.0.0" } }];
|
||||||
const result = await service.ensureInstalled({ plugins });
|
const result = await service.ensureInstalled({ plugins });
|
||||||
|
|
||||||
assert.equal(result.registryUrl, "https://registry.npmmirror.com");
|
assert.equal(result.registryUrl, "https://registry.npmmirror.com");
|
||||||
assert.ok(fs.existsSync(path.join(rootDir, "package.json")));
|
assert.ok(fs.existsSync(path.join(rootDir, "package.json")));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("installs direct dependency maps without plugin metadata", async () => {
|
it("installs direct dependency maps without plugin metadata", async () => {
|
||||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-direct-"));
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-direct-"));
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({ rootDir });
|
||||||
service.runtimeDepsRootDir = rootDir;
|
service.registryResolver = { async resolve() { return ""; } } as any;
|
||||||
service.registryResolver = {
|
|
||||||
async resolve() {
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
service.commandRunner = {
|
service.commandRunner = {
|
||||||
async run(command: string, args: string[]) {
|
async run(command: string, args: string[]) {
|
||||||
assert.equal(command, "pnpm");
|
assert.equal(command, "pnpm");
|
||||||
if (args.includes("--version")) {
|
if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; }
|
||||||
return { stdout: "9.1.0\n", stderr: "", code: 0 };
|
|
||||||
}
|
|
||||||
fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true });
|
fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true });
|
||||||
return { stdout: "", stderr: "", code: 0 };
|
return { stdout: "", stderr: "", code: 0 };
|
||||||
},
|
},
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
await service.ensureDependencies({ dependencies: { directPkg: "^1.0.0" } });
|
await service.ensureDependencies({ dependencies: { directPkg: "^1.0.0" } });
|
||||||
|
|
||||||
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
||||||
assert.deepEqual(manifest.dependencies, { directPkg: "^1.0.0" });
|
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(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, "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");
|
fs.writeFileSync(path.join(packageDir, "index.js"), "export const value = 42;\n", "utf8");
|
||||||
|
const service = new RuntimeDepsService({ rootDir });
|
||||||
const service = new RuntimeDepsService();
|
service.commandRunner = { async run() { throw new Error("install should not run"); } } as any;
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
service.commandRunner = {
|
|
||||||
async run() {
|
|
||||||
throw new Error("install should not run");
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const mod = await service.importRuntime("runtime-only");
|
const mod = await service.importRuntime("runtime-only");
|
||||||
|
|
||||||
assert.equal(mod.value, 42);
|
assert.equal(mod.value, 42);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("installs configured lazy dependency when import target is missing", async () => {
|
it("installs configured lazy dependency when import target is missing", async () => {
|
||||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-lazy-"));
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-lazy-"));
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({ rootDir, lazyDependencies: { "lazy-pkg": "^1.2.3" } });
|
||||||
service.runtimeDepsRootDir = rootDir;
|
service.registryResolver = { async resolve() { return ""; } } as any;
|
||||||
service.lazyDependencies = {
|
|
||||||
"lazy-pkg": "^1.2.3",
|
|
||||||
};
|
|
||||||
service.registryResolver = {
|
|
||||||
async resolve() {
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
service.commandRunner = {
|
service.commandRunner = {
|
||||||
async run(command: string, args: string[]) {
|
async run(command: string, args: string[]) {
|
||||||
assert.equal(command, "pnpm");
|
assert.equal(command, "pnpm");
|
||||||
if (args.includes("--version")) {
|
if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; }
|
||||||
return { stdout: "9.1.0\n", stderr: "", code: 0 };
|
|
||||||
}
|
|
||||||
const packageDir = path.join(rootDir, "node_modules", "lazy-pkg", "sub");
|
const packageDir = path.join(rootDir, "node_modules", "lazy-pkg", "sub");
|
||||||
fs.mkdirSync(packageDir, { recursive: true });
|
fs.mkdirSync(packageDir, { recursive: true });
|
||||||
fs.writeFileSync(path.join(rootDir, "node_modules", "lazy-pkg", "package.json"), JSON.stringify({ name: "lazy-pkg", type: "module" }), "utf8");
|
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 };
|
return { stdout: "", stderr: "", code: 0 };
|
||||||
},
|
},
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
const mod = await service.importRuntime("lazy-pkg/sub/entry.js");
|
const mod = await service.importRuntime("lazy-pkg/sub/entry.js");
|
||||||
|
|
||||||
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
||||||
assert.deepEqual(manifest.dependencies, { "lazy-pkg": "^1.2.3" });
|
assert.deepEqual(manifest.dependencies, { "lazy-pkg": "^1.2.3" });
|
||||||
assert.equal(mod.value, 7);
|
assert.equal(mod.value, 7);
|
||||||
@@ -140,22 +102,12 @@ describe("RuntimeDepsService", () => {
|
|||||||
|
|
||||||
it("resolves scoped package names for lazy imports", async () => {
|
it("resolves scoped package names for lazy imports", async () => {
|
||||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-scoped-"));
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-scoped-"));
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({ rootDir, lazyDependencies: { "@scope/lazy": "^2.0.0" } });
|
||||||
service.runtimeDepsRootDir = rootDir;
|
service.registryResolver = { async resolve() { return ""; } } as any;
|
||||||
service.lazyDependencies = {
|
|
||||||
"@scope/lazy": "^2.0.0",
|
|
||||||
};
|
|
||||||
service.registryResolver = {
|
|
||||||
async resolve() {
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
service.commandRunner = {
|
service.commandRunner = {
|
||||||
async run(command: string, args: string[]) {
|
async run(command: string, args: string[]) {
|
||||||
assert.equal(command, "pnpm");
|
assert.equal(command, "pnpm");
|
||||||
if (args.includes("--version")) {
|
if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; }
|
||||||
return { stdout: "9.1.0\n", stderr: "", code: 0 };
|
|
||||||
}
|
|
||||||
const packageDir = path.join(rootDir, "node_modules", "@scope", "lazy", "dist");
|
const packageDir = path.join(rootDir, "node_modules", "@scope", "lazy", "dist");
|
||||||
fs.mkdirSync(packageDir, { recursive: true });
|
fs.mkdirSync(packageDir, { recursive: true });
|
||||||
fs.writeFileSync(path.join(rootDir, "node_modules", "@scope", "lazy", "package.json"), JSON.stringify({ name: "@scope/lazy", type: "module" }), "utf8");
|
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 };
|
return { stdout: "", stderr: "", code: 0 };
|
||||||
},
|
},
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
const mod = await service.importRuntime("@scope/lazy/dist/index.js");
|
const mod = await service.importRuntime("@scope/lazy/dist/index.js");
|
||||||
|
|
||||||
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
||||||
assert.deepEqual(manifest.dependencies, { "@scope/lazy": "^2.0.0" });
|
assert.deepEqual(manifest.dependencies, { "@scope/lazy": "^2.0.0" });
|
||||||
assert.equal(mod.scoped, true);
|
assert.equal(mod.scoped, true);
|
||||||
@@ -173,82 +123,36 @@ describe("RuntimeDepsService", () => {
|
|||||||
|
|
||||||
it("reports missing lazy dependency configuration", async () => {
|
it("reports missing lazy dependency configuration", async () => {
|
||||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-lazy-missing-"));
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-lazy-missing-"));
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({ rootDir, lazyDependencies: {} });
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
service.lazyDependencies = {};
|
|
||||||
|
|
||||||
await assert.rejects(() => service.importRuntime("missing-pkg/sub.js"), /未配置懒加载版本: missing-pkg/);
|
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 () => {
|
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 rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-project-fallback-"));
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({ rootDir, lazyDependencies: {} });
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
service.lazyDependencies = {};
|
|
||||||
|
|
||||||
const mod = await service.importRuntime("dayjs");
|
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");
|
assert.equal(typeof mod.default, "function");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps previously installed dependencies when installing a later plugin", async () => {
|
it("keeps previously installed dependencies when installing a later plugin", async () => {
|
||||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-merge-"));
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-merge-"));
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({ rootDir });
|
||||||
service.runtimeDepsRootDir = rootDir;
|
service.registryResolver = { async resolve() { return ""; } } as any;
|
||||||
service.registryResolver = {
|
|
||||||
async resolve() {
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
service.commandRunner = {
|
service.commandRunner = {
|
||||||
async run(command: string, args: string[]) {
|
async run(command: string, args: string[]) {
|
||||||
assert.equal(command, "pnpm");
|
assert.equal(command, "pnpm");
|
||||||
if (args.includes("--version")) {
|
if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; }
|
||||||
return { stdout: "9.1.0\n", stderr: "", code: 0 };
|
|
||||||
}
|
|
||||||
fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true });
|
fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true });
|
||||||
return { stdout: "", stderr: "", code: 0 };
|
return { stdout: "", stderr: "", code: 0 };
|
||||||
},
|
},
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
await service.ensureInstalled({ plugins: [{ name: "a", pluginType: "deploy", dependPackages: { foo: "^1.0.0" } }] });
|
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" } }] });
|
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"));
|
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
||||||
assert.deepEqual(manifest.dependencies, {
|
assert.deepEqual(manifest.dependencies, { foo: "^1.0.0", bar: "^2.0.0" });
|
||||||
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();
|
const service = new RuntimeDepsService();
|
||||||
accessRegistry.register("runtimeDepsAccess", {
|
accessRegistry.register("runtimeDepsAccess", {
|
||||||
define: { name: "runtimeDepsAccess", title: "access", dependPackages: { accessOnly: "^1.0.0" } } as any,
|
define: { name: "runtimeDepsAccess", title: "access", dependPackages: { accessOnly: "^1.0.0" } } as any,
|
||||||
@@ -256,17 +160,11 @@ describe("RuntimeDepsService", () => {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const resolved = service.resolvePluginDependencies({
|
const resolved = service.resolvePluginDependencies({
|
||||||
name: "deploy",
|
name: "deploy", pluginType: "deploy", dependPlugins: { "access:runtimeDepsAccess": "*" },
|
||||||
pluginType: "deploy",
|
|
||||||
dependPlugins: { "access:runtimeDepsAccess": "*" },
|
|
||||||
dependPackages: { deployOnly: "^1.0.0" },
|
dependPackages: { deployOnly: "^1.0.0" },
|
||||||
});
|
});
|
||||||
const merged = service.collectDependencies(resolved);
|
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 {
|
} finally {
|
||||||
accessRegistry.unRegister("runtimeDepsAccess");
|
accessRegistry.unRegister("runtimeDepsAccess");
|
||||||
}
|
}
|
||||||
@@ -274,19 +172,12 @@ describe("RuntimeDepsService", () => {
|
|||||||
|
|
||||||
it("installs dependencies by registered plugin key", async () => {
|
it("installs dependencies by registered plugin key", async () => {
|
||||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-key-"));
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-key-"));
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({ rootDir });
|
||||||
service.runtimeDepsRootDir = rootDir;
|
service.registryResolver = { async resolve() { return ""; } } as any;
|
||||||
service.registryResolver = {
|
|
||||||
async resolve() {
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
service.commandRunner = {
|
service.commandRunner = {
|
||||||
async run(command: string, args: string[]) {
|
async run(command: string, args: string[]) {
|
||||||
assert.equal(command, "pnpm");
|
assert.equal(command, "pnpm");
|
||||||
if (args.includes("--version")) {
|
if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; }
|
||||||
return { stdout: "9.1.0\n", stderr: "", code: 0 };
|
|
||||||
}
|
|
||||||
fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true });
|
fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true });
|
||||||
return { stdout: "", stderr: "", code: 0 };
|
return { stdout: "", stderr: "", code: 0 };
|
||||||
},
|
},
|
||||||
@@ -296,8 +187,8 @@ describe("RuntimeDepsService", () => {
|
|||||||
target: async () => ({} as any),
|
target: async () => ({} as any),
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
|
service.setRegistries({ pluginRegistry, accessRegistry });
|
||||||
await service.ensureRuntimeDependencies({ pluginKeys: "plugin:runtimeDepsKey" });
|
await service.ensureRuntimeDependencies({ pluginKeys: "plugin:runtimeDepsKey" });
|
||||||
|
|
||||||
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
||||||
assert.deepEqual(manifest.dependencies, { keyed: "^1.0.0" });
|
assert.deepEqual(manifest.dependencies, { keyed: "^1.0.0" });
|
||||||
} finally {
|
} 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", () => {
|
it("reports missing dependent plugins", () => {
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService();
|
||||||
|
|
||||||
assert.throws(() => service.resolvePluginDependencies({ name: "deploy", pluginType: "deploy", dependPlugins: { "access:access": "*" } }), /插件依赖缺失/);
|
assert.throws(() => service.resolvePluginDependencies({ name: "deploy", pluginType: "deploy", dependPlugins: { "access:access": "*" } }), /插件依赖缺失/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -360,10 +209,8 @@ describe("RuntimeDepsService", () => {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() =>
|
() => service.resolvePluginDependencies({
|
||||||
service.resolvePluginDependencies({
|
name: "deploy", pluginType: "deploy",
|
||||||
name: "deploy",
|
|
||||||
pluginType: "deploy",
|
|
||||||
dependPlugins: { "access:runtimeDepsVersionedAccess": "^2.0.0" },
|
dependPlugins: { "access:runtimeDepsVersionedAccess": "^2.0.0" },
|
||||||
}),
|
}),
|
||||||
/插件依赖版本冲突/
|
/插件依赖版本冲突/
|
||||||
@@ -375,72 +222,9 @@ describe("RuntimeDepsService", () => {
|
|||||||
|
|
||||||
it("reports bare dependent plugin names as invalid format", () => {
|
it("reports bare dependent plugin names as invalid format", () => {
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService();
|
||||||
|
|
||||||
assert.throws(() => service.resolvePluginDependencies({ name: "deploy", pluginType: "deploy", dependPlugins: { runtimeDepsBareName: "*" } }), /插件依赖格式错误/);
|
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 () => {
|
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 rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-env-"));
|
||||||
const oldNodeOptions = process.env.NODE_OPTIONS;
|
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.NODE_OPTIONS = "--inspect=127.0.0.1:9229 --max-old-space-size=4096";
|
||||||
process.env.VSCODE_INSPECTOR_OPTIONS = '{"inspectorIpc":"test"}';
|
process.env.VSCODE_INSPECTOR_OPTIONS = '{"inspectorIpc":"test"}';
|
||||||
try {
|
try {
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({ rootDir });
|
||||||
service.runtimeDepsRootDir = rootDir;
|
service.registryResolver = { async resolve() { return ""; } } as any;
|
||||||
service.registryResolver = {
|
|
||||||
async resolve() {
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
service.commandRunner = {
|
service.commandRunner = {
|
||||||
async run(command: string, args: string[], options: { env?: NodeJS.ProcessEnv }) {
|
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?.NODE_OPTIONS, "--max-old-space-size=4096");
|
||||||
assert.equal(options.env?.VSCODE_INSPECTOR_OPTIONS, undefined);
|
assert.equal(options.env?.VSCODE_INSPECTOR_OPTIONS, undefined);
|
||||||
assert.equal(options.env?.CI, "true");
|
assert.equal(options.env?.CI, "true");
|
||||||
assert.equal(options.env?.pnpm_config_confirm_modules_purge, "false");
|
assert.equal(options.env?.pnpm_config_confirm_modules_purge, "false");
|
||||||
if (args.includes("--version")) {
|
if (args.includes("--version")) { return { stdout: "9.1.0\n", stderr: "", code: 0 }; }
|
||||||
return { stdout: "9.1.0\n", stderr: "", code: 0 };
|
|
||||||
}
|
|
||||||
return { stdout: "", stderr: "", code: 0 };
|
return { stdout: "", stderr: "", code: 0 };
|
||||||
},
|
},
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
await service.ensureInstalled({ plugins: [{ name: "a", dependPackages: { foo: "^1.0.0" } }] });
|
await service.ensureInstalled({ plugins: [{ name: "a", dependPackages: { foo: "^1.0.0" } }] });
|
||||||
} finally {
|
} finally {
|
||||||
if (oldNodeOptions == null) {
|
if (oldNodeOptions == null) { delete process.env.NODE_OPTIONS; }
|
||||||
delete process.env.NODE_OPTIONS;
|
else { process.env.NODE_OPTIONS = oldNodeOptions; }
|
||||||
} else {
|
if (oldInspectorOptions == null) { delete process.env.VSCODE_INSPECTOR_OPTIONS; }
|
||||||
process.env.NODE_OPTIONS = oldNodeOptions;
|
else { process.env.VSCODE_INSPECTOR_OPTIONS = oldInspectorOptions; }
|
||||||
}
|
}
|
||||||
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 () => {
|
it("rejects clearing unexpected runtime dependency path", async () => {
|
||||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-clear-invalid-"));
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-clear-invalid-"));
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({ rootDir });
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
|
|
||||||
await assert.rejects(() => service.clearRuntimeDeps(), /动态依赖目录配置异常/);
|
await assert.rejects(() => service.clearRuntimeDeps(), /动态依赖目录配置异常/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
+253
-213
@@ -1,15 +1,28 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { spawn } from "child_process";
|
import { spawn } from "child_process";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { Config, Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
|
||||||
import { createRequire } from "module";
|
import { createRequire } from "module";
|
||||||
import { pathToFileURL } from "url";
|
import { pathToFileURL } from "url";
|
||||||
import { NpmRegistryResolver } from "./npm-registry-resolver.js";
|
import { logger as defaultLogger } from "@certd/basic";
|
||||||
import { Registry, accessRegistry, notificationRegistry, pluginRegistry } from "@certd/pipeline";
|
import type { Registry } from "../registry/registry.js";
|
||||||
import { dnsProviderRegistry } from "@certd/plugin-lib";
|
export type ILogger = {
|
||||||
import { addonRegistry } from "@certd/lib-server";
|
info: (message: string) => void;
|
||||||
import { logger, ILogger } from "@certd/basic";
|
warn?: (message: string) => void;
|
||||||
|
error?: (message: string, ...args: any[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ImportRuntime = (specifier: string, logger?: ILogger) => Promise<any>;
|
||||||
|
|
||||||
|
export type EnsureRuntimeDepsOptions = {
|
||||||
|
pluginKeys: string | string[];
|
||||||
|
logger?: ILogger;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IRuntimeDepsService {
|
||||||
|
ensureRuntimeDependencies(options: EnsureRuntimeDepsOptions): Promise<any>;
|
||||||
|
importRuntime: ImportRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
export type RuntimeDependencyPluginDefine = {
|
export type RuntimeDependencyPluginDefine = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -30,22 +43,6 @@ type RegisteredDefineLike = RuntimeDependencyPluginDefine & {
|
|||||||
dependPackages?: Record<string, string>;
|
dependPackages?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
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 = {
|
type DependencyConflict = {
|
||||||
packageName: string;
|
packageName: string;
|
||||||
ranges: Array<{ pluginName: string; range: string }>;
|
ranges: Array<{ pluginName: string; range: string }>;
|
||||||
@@ -73,27 +70,111 @@ type CommandRunnerResult = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type CommandRunner = {
|
type CommandRunner = {
|
||||||
// @ts-ignore
|
|
||||||
run(command: string, args: string[], options: { cwd: string; timeoutMs: number; env?: NodeJS.ProcessEnv }): Promise<CommandRunnerResult>;
|
run(command: string, args: string[], options: { cwd: string; timeoutMs: number; env?: NodeJS.ProcessEnv }): Promise<CommandRunnerResult>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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<string> {
|
||||||
|
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<RegistryProbeResult> {
|
||||||
|
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<string, string>;
|
||||||
|
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<string, Promise<unknown>>();
|
const PROCESS_LOCKS = new Map<string, Promise<unknown>>();
|
||||||
|
|
||||||
class DefaultCommandRunner implements CommandRunner {
|
class DefaultCommandRunner implements CommandRunner {
|
||||||
// @ts-ignore
|
|
||||||
async run(command: string, args: string[], options: { cwd: string; timeoutMs: number; env?: NodeJS.ProcessEnv }): Promise<CommandRunnerResult> {
|
async run(command: string, args: string[], options: { cwd: string; timeoutMs: number; env?: NodeJS.ProcessEnv }): Promise<CommandRunnerResult> {
|
||||||
return await new Promise<CommandRunnerResult>(resolve => {
|
return await new Promise<CommandRunnerResult>(resolve => {
|
||||||
let stdout = "";
|
let stdout = "";
|
||||||
let stderr = "";
|
let stderr = "";
|
||||||
let settled = false;
|
let settled = false;
|
||||||
const child = spawn(command, args, {
|
const child = spawn(command, args, { cwd: options.cwd, env: options.env, windowsHide: true, shell: process.platform === "win32" });
|
||||||
cwd: options.cwd,
|
|
||||||
env: options.env,
|
|
||||||
windowsHide: true,
|
|
||||||
// @ts-ignore
|
|
||||||
shell: process.platform === "win32",
|
|
||||||
});
|
|
||||||
|
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
if (settled) {
|
if (settled) {
|
||||||
return;
|
return;
|
||||||
@@ -102,7 +183,6 @@ class DefaultCommandRunner implements CommandRunner {
|
|||||||
child.kill("SIGTERM");
|
child.kill("SIGTERM");
|
||||||
resolve({ stdout, stderr: stderr || `command timeout after ${options.timeoutMs}ms`, code: 1 });
|
resolve({ stdout, stderr: stderr || `command timeout after ${options.timeoutMs}ms`, code: 1 });
|
||||||
}, options.timeoutMs);
|
}, options.timeoutMs);
|
||||||
|
|
||||||
child.stdout?.on("data", chunk => {
|
child.stdout?.on("data", chunk => {
|
||||||
stdout += chunk.toString();
|
stdout += chunk.toString();
|
||||||
});
|
});
|
||||||
@@ -129,43 +209,55 @@ class DefaultCommandRunner implements CommandRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provide()
|
|
||||||
@Scope(ScopeEnum.Singleton)
|
|
||||||
export class RuntimeDepsService {
|
export class RuntimeDepsService {
|
||||||
@Config("runtimeDeps.rootDir")
|
runtimeDepsRootDir: string;
|
||||||
runtimeDepsRootDir = "./data/.runtime-deps";
|
autoInstall: boolean;
|
||||||
|
enabled: boolean;
|
||||||
@Config("runtimeDeps.autoInstall")
|
installTimeoutMs: number;
|
||||||
autoInstall = true;
|
pnpmCommand: string;
|
||||||
|
lazyDependencies: Record<string, string>;
|
||||||
@Config("runtimeDeps.enabled")
|
|
||||||
enabled = true;
|
|
||||||
|
|
||||||
@Config("runtimeDeps.installTimeoutMs")
|
|
||||||
installTimeoutMs = 120000;
|
|
||||||
|
|
||||||
@Config("runtimeDeps.pnpmCommand")
|
|
||||||
pnpmCommand = "";
|
|
||||||
|
|
||||||
@Config("runtimeDeps.lazyDependencies")
|
|
||||||
lazyDependencies: Record<string, string> = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从插件 registry 收集到的懒加载依赖,键为包名,值为版本范围
|
|
||||||
*/
|
|
||||||
pluginLazyDependencies: Record<string, string> = {};
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
registryResolver!: NpmRegistryResolver;
|
registryResolver!: NpmRegistryResolver;
|
||||||
|
|
||||||
commandRunner: CommandRunner = new DefaultCommandRunner();
|
commandRunner: CommandRunner = new DefaultCommandRunner();
|
||||||
|
pluginLazyDependencies: Record<string, string> = {};
|
||||||
private installPromises = new Map<string, Promise<InstallResult>>();
|
private installPromises = new Map<string, Promise<InstallResult>>();
|
||||||
|
private registriesMap: Record<string, { registry: Registry<any>; 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<any>; accessRegistry?: Registry<any>; notificationRegistry?: Registry<any>; dnsProviderRegistry?: Registry<any>; addonRegistry?: Registry<any> }) {
|
||||||
|
const map: Record<string, { registry: Registry<any>; 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 {
|
collectDependencies(plugins: RuntimeDependencyPluginDefine[]): CollectDependenciesResult {
|
||||||
const merged: Record<string, string> = {};
|
const merged: Record<string, string> = {};
|
||||||
const seen: Record<string, Array<{ pluginName: string; range: string }>> = {};
|
const seen: Record<string, Array<{ pluginName: string; range: string }>> = {};
|
||||||
|
|
||||||
for (const plugin of plugins) {
|
for (const plugin of plugins) {
|
||||||
const deps = plugin.dependPackages || {};
|
const deps = plugin.dependPackages || {};
|
||||||
for (const [packageName, range] of Object.entries(deps)) {
|
for (const [packageName, range] of Object.entries(deps)) {
|
||||||
@@ -173,7 +265,6 @@ export class RuntimeDepsService {
|
|||||||
seen[packageName].push({ pluginName: plugin.name, range });
|
seen[packageName].push({ pluginName: plugin.name, range });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const conflicts: DependencyConflict[] = [];
|
const conflicts: DependencyConflict[] = [];
|
||||||
for (const [packageName, ranges] of Object.entries(seen)) {
|
for (const [packageName, ranges] of Object.entries(seen)) {
|
||||||
const first = ranges[0]?.range;
|
const first = ranges[0]?.range;
|
||||||
@@ -187,7 +278,6 @@ export class RuntimeDepsService {
|
|||||||
}
|
}
|
||||||
merged[packageName] = first;
|
merged[packageName] = first;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { dependencies: merged, conflicts };
|
return { dependencies: merged, conflicts };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,16 +294,10 @@ export class RuntimeDepsService {
|
|||||||
async ensureDependencies(options: { dependencies: Record<string, string>; logger?: ILogger }): Promise<InstallResult> {
|
async ensureDependencies(options: { dependencies: Record<string, string>; logger?: ILogger }): Promise<InstallResult> {
|
||||||
const { dependencies, logger: log } = options;
|
const { dependencies, logger: log } = options;
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
return {
|
return { registryUrl: "", packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json") };
|
||||||
registryUrl: "",
|
|
||||||
packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json"),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
if (!this.autoInstall) {
|
if (!this.autoInstall) {
|
||||||
return {
|
return { registryUrl: "", packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json") };
|
||||||
registryUrl: "",
|
|
||||||
packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json"),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
const dependenciesHash = this.createDependenciesHash(dependencies);
|
const dependenciesHash = this.createDependenciesHash(dependencies);
|
||||||
let installPromise = this.installPromises.get(dependenciesHash);
|
let installPromise = this.installPromises.get(dependenciesHash);
|
||||||
@@ -244,94 +328,21 @@ export class RuntimeDepsService {
|
|||||||
const keys = Array.isArray(pluginKeys) ? pluginKeys : [pluginKeys];
|
const keys = Array.isArray(pluginKeys) ? pluginKeys : [pluginKeys];
|
||||||
const pluginDefines = keys.map(pluginKey => this.getDefineByPluginKey(pluginKey));
|
const pluginDefines = keys.map(pluginKey => this.getDefineByPluginKey(pluginKey));
|
||||||
if (pluginDefines.every(pluginDefine => !pluginDefine.dependPackages && !pluginDefine.dependPlugins)) {
|
if (pluginDefines.every(pluginDefine => !pluginDefine.dependPackages && !pluginDefine.dependPlugins)) {
|
||||||
return {
|
return { registryUrl: "", packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json") };
|
||||||
registryUrl: "",
|
|
||||||
packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json"),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
const expandedPluginDefines = pluginDefines.flatMap(pluginDefine => this.resolvePluginDependencies(pluginDefine));
|
const expandedPluginDefines = pluginDefines.flatMap(pluginDefine => this.resolvePluginDependencies(pluginDefine));
|
||||||
return await this.ensureInstalled({ plugins: expandedPluginDefines, logger: log });
|
return await this.ensureInstalled({ plugins: expandedPluginDefines, logger: log });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doEnsureInstalled(options: { dependencies: Record<string, string>; logger?: ILogger }): Promise<InstallResult> {
|
async importRuntime(specifier: string, logger: ILogger = defaultLogger) {
|
||||||
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) {
|
|
||||||
if (this.isNativeImportSpecifier(specifier)) {
|
if (this.isNativeImportSpecifier(specifier)) {
|
||||||
return await import(specifier);
|
return await import(specifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolved = await this.resolveImportSpecifier(specifier, logger);
|
const resolved = await this.resolveImportSpecifier(specifier, logger);
|
||||||
return await import(pathToFileURL(resolved).href);
|
return await import(pathToFileURL(resolved).href);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async resolveImportSpecifier(specifier: string, logger?: ILogger) {
|
private async resolveImportSpecifier(specifier: string, logger: ILogger = defaultLogger) {
|
||||||
try {
|
try {
|
||||||
return this.resolveRuntimeSpecifier(specifier).resolved;
|
return this.resolveRuntimeSpecifier(specifier).resolved;
|
||||||
} catch (runtimeError: any) {
|
} catch (runtimeError: any) {
|
||||||
@@ -410,10 +421,7 @@ export class RuntimeDepsService {
|
|||||||
if (!range) {
|
if (!range) {
|
||||||
throw new Error(`动态依赖未安装且未配置懒加载版本: ${packageName}`);
|
throw new Error(`动态依赖未安装且未配置懒加载版本: ${packageName}`);
|
||||||
}
|
}
|
||||||
const dependencies = {
|
await this.ensureDependencies({ dependencies: { [packageName]: range }, logger });
|
||||||
[packageName]: range,
|
|
||||||
};
|
|
||||||
await this.ensureDependencies({ dependencies, logger });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private isModuleNotFoundError(error: any) {
|
private isModuleNotFoundError(error: any) {
|
||||||
@@ -423,7 +431,6 @@ export class RuntimeDepsService {
|
|||||||
resolvePluginDependencies(current: RuntimeDependencyPluginDefine): RuntimeDependencyPluginDefine[] {
|
resolvePluginDependencies(current: RuntimeDependencyPluginDefine): RuntimeDependencyPluginDefine[] {
|
||||||
const resolved: RuntimeDependencyPluginDefine[] = [];
|
const resolved: RuntimeDependencyPluginDefine[] = [];
|
||||||
const visited = new Set<string>();
|
const visited = new Set<string>();
|
||||||
|
|
||||||
const visit = (item: RuntimeDependencyPluginDefine) => {
|
const visit = (item: RuntimeDependencyPluginDefine) => {
|
||||||
const key = this.buildPluginDependencyKey(item);
|
const key = this.buildPluginDependencyKey(item);
|
||||||
if (visited.has(key)) {
|
if (visited.has(key)) {
|
||||||
@@ -439,7 +446,6 @@ export class RuntimeDepsService {
|
|||||||
visit(dependency);
|
visit(dependency);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
visit(current);
|
visit(current);
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
@@ -454,35 +460,81 @@ export class RuntimeDepsService {
|
|||||||
|
|
||||||
private getDefineByPluginKey(pluginKey: string, owner?: RuntimeDependencyPluginDefine): RuntimeDependencyPluginDefine {
|
private getDefineByPluginKey(pluginKey: string, owner?: RuntimeDependencyPluginDefine): RuntimeDependencyPluginDefine {
|
||||||
const parts = pluginKey.split(":");
|
const parts = pluginKey.split(":");
|
||||||
const [pluginType, subtype, rawName] = parts;
|
let pluginType: string, name: string, subtype: string | undefined;
|
||||||
let name = rawName;
|
|
||||||
if (parts.length === 2) {
|
if (parts.length === 2) {
|
||||||
name = subtype;
|
[pluginType, name] = parts;
|
||||||
} else if (parts.length === 3) {
|
} else if (parts.length === 3) {
|
||||||
//无修改
|
[pluginType, subtype, name] = parts;
|
||||||
} else {
|
} else {
|
||||||
const ownerName = owner?.name || pluginKey;
|
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<string, { registry: Registry<any>; key: string; pluginType: string; addonType?: string }> = {
|
if (!this.registriesMap) {
|
||||||
plugin: { registry: pluginRegistry, key: name, pluginType: "plugin" },
|
throw new Error("注册表未设置,请先调用 setRegistries");
|
||||||
access: { registry: accessRegistry, key: name, pluginType: "access" },
|
}
|
||||||
notification: { registry: notificationRegistry, key: name, pluginType: "notification" },
|
const target = this.registriesMap[pluginType];
|
||||||
dnsProvider: { registry: dnsProviderRegistry, key: name, pluginType: "dnsProvider" },
|
|
||||||
addon: { registry: addonRegistry, key: `${subtype}:${name}`, pluginType: "addon", addonType: subtype },
|
|
||||||
};
|
|
||||||
const target = registryMap[pluginType];
|
|
||||||
if (!target) {
|
if (!target) {
|
||||||
const ownerName = owner?.name || pluginKey;
|
const ownerName = owner?.name || pluginKey;
|
||||||
throw new Error(`插件依赖格式错误: ${ownerName} 依赖 ${pluginKey},未知插件类型 ${pluginType}`);
|
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) {
|
if (!define) {
|
||||||
throw new Error(`插件依赖缺失: ${owner?.name || pluginKey} 依赖 ${pluginKey},但该插件未注册或已禁用`);
|
throw new Error(`插件依赖缺失: ${owner?.name || pluginKey} 依赖 ${pluginKey},但该插件未注册或已禁用`);
|
||||||
}
|
}
|
||||||
return { ...define, key: pluginKey, pluginType: target.pluginType, addonType: target.addonType };
|
return { ...define, key: pluginKey, pluginType: target.pluginType, addonType: target.addonType };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async doEnsureInstalled(options: { dependencies: Record<string, string>; logger?: ILogger }): Promise<InstallResult> {
|
||||||
|
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<T>(run: () => Promise<T>): Promise<T> {
|
private async withInstallLock<T>(run: () => Promise<T>): Promise<T> {
|
||||||
const rootDir = this.getRuntimeDepsRootDir();
|
const rootDir = this.getRuntimeDepsRootDir();
|
||||||
fs.mkdirSync(rootDir, { recursive: true });
|
fs.mkdirSync(rootDir, { recursive: true });
|
||||||
@@ -508,9 +560,7 @@ export class RuntimeDepsService {
|
|||||||
} catch {
|
} catch {
|
||||||
try {
|
try {
|
||||||
fs.rmSync(lockFile, { force: true });
|
fs.rmSync(lockFile, { force: true });
|
||||||
} catch {
|
} catch {}
|
||||||
// Windows 下 closeSync 后文件句柄可能未立即释放,忽略清理失败
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
releaseProcessLock();
|
releaseProcessLock();
|
||||||
@@ -525,7 +575,6 @@ export class RuntimeDepsService {
|
|||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
const fd = fs.openSync(lockFile, "wx");
|
const fd = fs.openSync(lockFile, "wx");
|
||||||
// @ts-ignore
|
|
||||||
fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }), "utf8");
|
fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }), "utf8");
|
||||||
return fd;
|
return fd;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -562,8 +611,7 @@ export class RuntimeDepsService {
|
|||||||
if (entry === ".install.lock") {
|
if (entry === ".install.lock") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const entryPath = path.join(rootDir, entry);
|
fs.rmSync(path.join(rootDir, entry), { recursive: true, force: true });
|
||||||
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.installPromises.clear();
|
this.installPromises.clear();
|
||||||
@@ -571,28 +619,16 @@ export class RuntimeDepsService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并 package.json 的 lazyDependencies 和插件注册表收集到的 dependPackages
|
|
||||||
* 插件依赖优先(同名时使用插件声明的版本)
|
|
||||||
*/
|
|
||||||
getMergedLazyDependencies(): Record<string, string> {
|
getMergedLazyDependencies(): Record<string, string> {
|
||||||
return { ...this.lazyDependencies, ...this.pluginLazyDependencies };
|
return { ...this.lazyDependencies, ...this.pluginLazyDependencies };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
collectPluginDeps(logger?: ILogger) {
|
||||||
* 从所有插件注册表中收集 dependPackages,合并到 pluginLazyDependencies
|
if (!this.registriesMap) {
|
||||||
*/
|
return;
|
||||||
collectPluginDeps() {
|
}
|
||||||
const registries: Array<{ registry: Registry<any>; keyFormatter?: (name: string) => string }> = [
|
|
||||||
{ registry: pluginRegistry },
|
|
||||||
{ registry: accessRegistry },
|
|
||||||
{ registry: notificationRegistry },
|
|
||||||
{ registry: dnsProviderRegistry },
|
|
||||||
{ registry: addonRegistry },
|
|
||||||
];
|
|
||||||
|
|
||||||
const deps: Record<string, string> = {};
|
const deps: Record<string, string> = {};
|
||||||
for (const { registry } of registries) {
|
for (const { registry } of Object.values(this.registriesMap)) {
|
||||||
const defineList = registry.getDefineList();
|
const defineList = registry.getDefineList();
|
||||||
for (const define of defineList) {
|
for (const define of defineList) {
|
||||||
const dependPackages = (define as any).dependPackages as Record<string, string> | undefined;
|
const dependPackages = (define as any).dependPackages as Record<string, string> | undefined;
|
||||||
@@ -602,23 +638,19 @@ export class RuntimeDepsService {
|
|||||||
for (const [pkgName, range] of Object.entries(dependPackages)) {
|
for (const [pkgName, range] of Object.entries(dependPackages)) {
|
||||||
const existing = deps[pkgName];
|
const existing = deps[pkgName];
|
||||||
if (existing && !areRangesCompatible(existing, range)) {
|
if (existing && !areRangesCompatible(existing, range)) {
|
||||||
logger.warn(`懒加载依赖版本冲突: ${pkgName} => ${existing} vs ${range},保留已有版本`);
|
(logger || defaultLogger).warn?.(`懒加载依赖版本冲突: ${pkgName} => ${existing} vs ${range},保留已有版本`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
deps[pkgName] = range;
|
deps[pkgName] = range;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pluginLazyDependencies = deps;
|
this.pluginLazyDependencies = deps;
|
||||||
logger.info(`从插件注册表收集到 ${Object.keys(deps).length} 个懒加载依赖`);
|
(logger || defaultLogger).info(`从插件注册表收集到 ${Object.keys(deps).length} 个懒加载依赖`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
refreshPluginDeps(logger?: ILogger) {
|
||||||
* 刷新插件懒加载依赖:收集所有插件的 dependPackages 到内存列表,实际安装延迟到 importRuntime 时触发
|
this.collectPluginDeps(logger);
|
||||||
*/
|
|
||||||
async refreshPluginDeps() {
|
|
||||||
this.collectPluginDeps();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readInstallState(statePath: string): any {
|
private readInstallState(statePath: string): any {
|
||||||
@@ -660,14 +692,8 @@ export class RuntimeDepsService {
|
|||||||
return dependencies;
|
return dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
private async getPnpmVersion(command: string, env: NodeJS.ProcessEnv) {
|
private async getPnpmVersion(command: string, env: NodeJS.ProcessEnv) {
|
||||||
const rootDir = this.getRuntimeDepsRootDir();
|
const result = await this.commandRunner.run(command, ["--version"], { cwd: this.getRuntimeDepsRootDir(), timeoutMs: Math.min(this.installTimeoutMs, 10000), env });
|
||||||
const result = await this.commandRunner.run(command, ["--version"], {
|
|
||||||
cwd: rootDir,
|
|
||||||
timeoutMs: Math.min(this.installTimeoutMs, 10000),
|
|
||||||
env,
|
|
||||||
});
|
|
||||||
if (result.code !== 0) {
|
if (result.code !== 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -675,14 +701,10 @@ export class RuntimeDepsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getPnpmCommand() {
|
private getPnpmCommand() {
|
||||||
if (this.pnpmCommand) {
|
return this.pnpmCommand || "pnpm";
|
||||||
return this.pnpmCommand;
|
|
||||||
}
|
|
||||||
return "pnpm";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildChildEnv(registryUrl: string) {
|
private buildChildEnv(registryUrl: string) {
|
||||||
// @ts-ignore
|
|
||||||
const env = { ...process.env };
|
const env = { ...process.env };
|
||||||
for (const key of ["NODE_OPTIONS", "VSCODE_INSPECTOR_OPTIONS", "NODE_INSPECTOR_PORT", "NODE_DEBUG"]) {
|
for (const key of ["NODE_OPTIONS", "VSCODE_INSPECTOR_OPTIONS", "NODE_INSPECTOR_PORT", "NODE_DEBUG"]) {
|
||||||
if (!env[key]) {
|
if (!env[key]) {
|
||||||
@@ -713,7 +735,7 @@ export class RuntimeDepsService {
|
|||||||
.join(" ");
|
.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRuntimeDepsRootDir() {
|
getRuntimeDepsRootDir() {
|
||||||
return path.resolve(this.runtimeDepsRootDir);
|
return path.resolve(this.runtimeDepsRootDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -731,3 +753,21 @@ function isPluginVersionCompatible(plugin: RuntimeDependencyPluginDefine, expect
|
|||||||
}
|
}
|
||||||
return areRangesCompatible(expectedRange, plugin.version);
|
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<any> {
|
||||||
|
return getRuntimeDepsService().importRuntime(specifier, logger);
|
||||||
|
}
|
||||||
@@ -1,27 +1,3 @@
|
|||||||
/**
|
// 类型已迁至 runtime-deps-service.ts,通过 index.ts 重新导出
|
||||||
* 运行时动态导入函数类型
|
// 此文件保留空壳,避免已有引用报错
|
||||||
*/
|
export * from "./runtime-deps-service.js";
|
||||||
export type ImportRuntime = (specifier: string, logger?: ILogger) => Promise<any>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志接口
|
|
||||||
*/
|
|
||||||
export type ILogger = {
|
|
||||||
info: (message: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 运行时依赖服务参数
|
|
||||||
*/
|
|
||||||
export type EnsureRuntimeDepsOptions = {
|
|
||||||
pluginKeys: string | string[];
|
|
||||||
logger?: ILogger;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 运行时依赖服务接口
|
|
||||||
*/
|
|
||||||
export interface IRuntimeDepsService {
|
|
||||||
ensureRuntimeDependencies(options: EnsureRuntimeDepsOptions): Promise<any>;
|
|
||||||
importRuntime: ImportRuntime;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,7 +28,8 @@
|
|||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"esmock": "^2.7.5",
|
"esmock": "^2.7.5",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
"tslib": "^2.8.1"
|
"tslib": "^2.8.1",
|
||||||
|
"eslint": "^8.57.0"
|
||||||
},
|
},
|
||||||
"gitHead": "a6ef6996c3a68ce0c2a4577a0934ed74a53f0515"
|
"gitHead": "268cd6cc9cb4f1f3d5d5d77859a82f18f0cb6db7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,5 +37,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "a6ef6996c3a68ce0c2a4577a0934ed74a53f0515"
|
"gitHead": "268cd6cc9cb4f1f3d5d5d77859a82f18f0cb6db7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,8 @@
|
|||||||
"js-yaml": "^3.11.0",
|
"js-yaml": "^3.11.0",
|
||||||
"mocha": "^10.6.0",
|
"mocha": "^10.6.0",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
"tslib": "^2.8.1"
|
"tslib": "^2.8.1",
|
||||||
|
"eslint": "^8.57.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8.6.0",
|
"node": ">= 8.6.0",
|
||||||
@@ -62,5 +63,5 @@
|
|||||||
"fetch"
|
"fetch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"gitHead": "a6ef6996c3a68ce0c2a4577a0934ed74a53f0515"
|
"gitHead": "268cd6cc9cb4f1f3d5d5d77859a82f18f0cb6db7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,5 +38,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "a6ef6996c3a68ce0c2a4577a0934ed74a53f0515"
|
"gitHead": "268cd6cc9cb4f1f3d5d5d77859a82f18f0cb6db7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,5 +69,5 @@
|
|||||||
"typeorm": "^0.3.20",
|
"typeorm": "^0.3.20",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "a6ef6996c3a68ce0c2a4577a0934ed74a53f0515"
|
"gitHead": "268cd6cc9cb4f1f3d5d5d77859a82f18f0cb6db7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
import { HttpClient, ILogger, utils } from "@certd/basic";
|
import { HttpClient, ILogger, utils } from "@certd/basic";
|
||||||
import {upperFirst} from "lodash-es";
|
import { upperFirst } from "lodash-es";
|
||||||
import {
|
import { accessRegistry, FormItemProps, IAccessService, IServiceGetter, PluginRequestHandleReq, Registrable, getRuntimeDepsService } from "@certd/pipeline";
|
||||||
accessRegistry,
|
|
||||||
FormItemProps,
|
|
||||||
IAccessService,
|
|
||||||
IRuntimeDepsService,
|
|
||||||
IServiceGetter,
|
|
||||||
PluginRequestHandleReq,
|
|
||||||
Registrable
|
|
||||||
} from "@certd/pipeline";
|
|
||||||
|
|
||||||
|
|
||||||
export type AddonRequestHandleReqInput<T = any> = {
|
export type AddonRequestHandleReqInput<T = any> = {
|
||||||
id?: number;
|
id?: number;
|
||||||
@@ -19,7 +10,7 @@ export type AddonRequestHandleReqInput<T = any> = {
|
|||||||
|
|
||||||
export type AddonRequestHandleReq<T = any> = {
|
export type AddonRequestHandleReq<T = any> = {
|
||||||
addonType: string;
|
addonType: string;
|
||||||
} &PluginRequestHandleReq<AddonRequestHandleReqInput<T>>;
|
} & PluginRequestHandleReq<AddonRequestHandleReqInput<T>>;
|
||||||
|
|
||||||
export type AddonInputDefine = FormItemProps & {
|
export type AddonInputDefine = FormItemProps & {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -48,8 +39,6 @@ export type AddonInstanceConfig = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface IAddon {
|
export interface IAddon {
|
||||||
ctx: AddonContext;
|
ctx: AddonContext;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
@@ -67,13 +56,9 @@ export abstract class BaseAddon implements IAddon {
|
|||||||
ctx!: AddonContext;
|
ctx!: AddonContext;
|
||||||
http!: HttpClient;
|
http!: HttpClient;
|
||||||
logger!: ILogger;
|
logger!: ILogger;
|
||||||
runtimeDepsService?: IRuntimeDepsService;
|
|
||||||
|
|
||||||
async importRuntime(specifier: string) {
|
async importRuntime(specifier: string) {
|
||||||
if (!this.runtimeDepsService) {
|
return await getRuntimeDepsService().importRuntime(specifier, this.logger);
|
||||||
return await import(specifier);
|
|
||||||
}
|
|
||||||
return await this.runtimeDepsService.importRuntime(specifier, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
title!: string;
|
title!: string;
|
||||||
@@ -85,7 +70,7 @@ export abstract class BaseAddon implements IAddon {
|
|||||||
if (accessId == null) {
|
if (accessId == null) {
|
||||||
throw new Error("您还没有配置授权");
|
throw new Error("您还没有配置授权");
|
||||||
}
|
}
|
||||||
const accessService = await this.ctx.serviceGetter.get<IAccessService>("accessService")
|
const accessService = await this.ctx.serviceGetter.get<IAccessService>("accessService");
|
||||||
let res: any = null;
|
let res: any = null;
|
||||||
if (isCommon) {
|
if (isCommon) {
|
||||||
res = await accessService.getCommonById(accessId);
|
res = await accessService.getCommonById(accessId);
|
||||||
@@ -118,15 +103,12 @@ export abstract class BaseAddon implements IAddon {
|
|||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.http = ctx.http;
|
this.http = ctx.http;
|
||||||
this.logger = ctx.logger;
|
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;
|
this.define = define;
|
||||||
};
|
};
|
||||||
|
|
||||||
async onRequest(req:AddonRequestHandleReq) {
|
async onRequest(req: AddonRequestHandleReq) {
|
||||||
if (!req.action) {
|
if (!req.action) {
|
||||||
throw new Error("action is required");
|
throw new Error("action is required");
|
||||||
}
|
}
|
||||||
@@ -144,10 +126,8 @@ export abstract class BaseAddon implements IAddon {
|
|||||||
}
|
}
|
||||||
throw new Error(`action ${req.action} not found`);
|
throw new Error(`action ${req.action} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IAddonGetter {
|
export interface IAddonGetter {
|
||||||
getById<T = any>(id: any): Promise<T>;
|
getById<T = any>(id: any): Promise<T>;
|
||||||
getCommonById<T = any>(id: any): Promise<T>;
|
getCommonById<T = any>(id: any): Promise<T>;
|
||||||
|
|||||||
@@ -52,5 +52,5 @@
|
|||||||
"typeorm": "^0.3.20",
|
"typeorm": "^0.3.20",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "a6ef6996c3a68ce0c2a4577a0934ed74a53f0515"
|
"gitHead": "268cd6cc9cb4f1f3d5d5d77859a82f18f0cb6db7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as path from 'path';
|
import * as path from "path";
|
||||||
import * as fs from 'fs';
|
import * as fs from "fs";
|
||||||
import { QueryRunner, Table } from 'typeorm';
|
import { QueryRunner, Table } from "typeorm";
|
||||||
import { FlywayHistory } from './entity.js';
|
import { FlywayHistory } from "./entity.js";
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from "crypto";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 脚本文件信息
|
* 脚本文件信息
|
||||||
@@ -32,10 +32,10 @@ const DefaultLogger = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let customLogger:any = null;
|
let customLogger: any = null;
|
||||||
export function setFlywayLogger (logger: any) {
|
export function setFlywayLogger(logger: any) {
|
||||||
customLogger = logger;
|
customLogger = logger;
|
||||||
};
|
}
|
||||||
|
|
||||||
export class Flyway {
|
export class Flyway {
|
||||||
scriptDir;
|
scriptDir;
|
||||||
@@ -45,8 +45,8 @@ export class Flyway {
|
|||||||
connection;
|
connection;
|
||||||
logger;
|
logger;
|
||||||
constructor(opts: any) {
|
constructor(opts: any) {
|
||||||
this.scriptDir = opts.scriptDir ?? 'db/migration';
|
this.scriptDir = opts.scriptDir ?? "db/migration";
|
||||||
this.flywayTableName = opts.flywayTableName ?? 'flyway_history';
|
this.flywayTableName = opts.flywayTableName ?? "flyway_history";
|
||||||
this.baseline = opts.baseline ?? false;
|
this.baseline = opts.baseline ?? false;
|
||||||
this.allowHashNotMatch = opts.allowHashNotMatch ?? false;
|
this.allowHashNotMatch = opts.allowHashNotMatch ?? false;
|
||||||
this.logger = customLogger || opts.logger || DefaultLogger;
|
this.logger = customLogger || opts.logger || DefaultLogger;
|
||||||
@@ -54,9 +54,9 @@ export class Flyway {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async run(ignores?: (RegExp | string)[]) {
|
async run(ignores?: (RegExp | string)[]) {
|
||||||
this.logger.info('[ midfly ] start-------------');
|
this.logger.info("[ midfly ] start-------------");
|
||||||
if (!fs.existsSync(this.scriptDir)) {
|
if (!fs.existsSync(this.scriptDir)) {
|
||||||
this.logger.info('[ midfly ] scriptDir<' + this.scriptDir + '> not found');
|
this.logger.info("[ midfly ] scriptDir<" + this.scriptDir + "> not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ export class Flyway {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!file.isBaseline) {
|
if (!file.isBaseline) {
|
||||||
this.logger.info('need exec script file: ', file.script);
|
this.logger.info("need exec script file: ", file.script);
|
||||||
//执行sql文件
|
//执行sql文件
|
||||||
if (/\.sql$/.test(file.script)) {
|
if (/\.sql$/.test(file.script)) {
|
||||||
await this.execSql(filepath, queryRunner);
|
await this.execSql(filepath, queryRunner);
|
||||||
@@ -87,7 +87,7 @@ export class Flyway {
|
|||||||
// await this.execJsOrTs(filepath, t);
|
// await this.execJsOrTs(filepath, t);
|
||||||
// }
|
// }
|
||||||
} else {
|
} else {
|
||||||
this.logger.info('baseline script file: ', file.script);
|
this.logger.info("baseline script file: ", file.script);
|
||||||
}
|
}
|
||||||
await this.storeSqlExecLog(file.script, filepath, true, queryRunner);
|
await this.storeSqlExecLog(file.script, filepath, true, queryRunner);
|
||||||
await queryRunner.commitTransaction();
|
await queryRunner.commitTransaction();
|
||||||
@@ -95,10 +95,15 @@ export class Flyway {
|
|||||||
this.logger.error(err);
|
this.logger.error(err);
|
||||||
await this.storeSqlExecLog(file.script, filepath, false, queryRunner);
|
await this.storeSqlExecLog(file.script, filepath, false, queryRunner);
|
||||||
await queryRunner.rollbackTransaction();
|
await queryRunner.rollbackTransaction();
|
||||||
|
|
||||||
|
if (err.code === "SQLITE_IOERR_WRITE") {
|
||||||
|
this.logger.warn("SQLite数据库写入失败,可能您的操作系统版本太低,请将「certd:latest」镜像改为「certd:slim」即可。(如需指定版本可以修改成「certd:[version]-slim」)", file.script);
|
||||||
|
}
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.logger.info('[ midfly ] end-------------');
|
this.logger.info("[ midfly ] end-------------");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async storeSqlExecLog(filename: string, filepath: string, success: boolean, queryRunner: QueryRunner) {
|
private async storeSqlExecLog(filename: string, filepath: string, success: boolean, queryRunner: QueryRunner) {
|
||||||
@@ -160,17 +165,17 @@ export class Flyway {
|
|||||||
name: this.flywayTableName,
|
name: this.flywayTableName,
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
name: 'id',
|
name: "id",
|
||||||
type: this.connection.driver.normalizeType({
|
type: this.connection.driver.normalizeType({
|
||||||
type: this.connection.driver.mappedDataTypes.migrationId,
|
type: this.connection.driver.mappedDataTypes.migrationId,
|
||||||
}),
|
}),
|
||||||
isGenerated: true,
|
isGenerated: true,
|
||||||
generationStrategy: 'increment',
|
generationStrategy: "increment",
|
||||||
isPrimary: true,
|
isPrimary: true,
|
||||||
isNullable: false,
|
isNullable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'timestamp',
|
name: "timestamp",
|
||||||
type: this.connection.driver.normalizeType({
|
type: this.connection.driver.normalizeType({
|
||||||
type: this.connection.driver.mappedDataTypes.migrationTimestamp,
|
type: this.connection.driver.mappedDataTypes.migrationTimestamp,
|
||||||
}),
|
}),
|
||||||
@@ -178,23 +183,23 @@ export class Flyway {
|
|||||||
isNullable: false,
|
isNullable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: "name",
|
||||||
type: this.connection.driver.normalizeType({
|
type: this.connection.driver.normalizeType({
|
||||||
type: this.connection.driver.mappedDataTypes.migrationName,
|
type: this.connection.driver.mappedDataTypes.migrationName,
|
||||||
}),
|
}),
|
||||||
isNullable: false,
|
isNullable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'hash',
|
name: "hash",
|
||||||
type: this.connection.driver.normalizeType({
|
type: this.connection.driver.normalizeType({
|
||||||
type: this.connection.driver.mappedDataTypes.migrationName,
|
type: this.connection.driver.mappedDataTypes.migrationName,
|
||||||
}),
|
}),
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'success',
|
name: "success",
|
||||||
type: this.connection.driver.normalizeType({
|
type: this.connection.driver.normalizeType({
|
||||||
type: 'boolean',
|
type: "boolean",
|
||||||
}),
|
}),
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
},
|
},
|
||||||
@@ -210,7 +215,7 @@ export class Flyway {
|
|||||||
}
|
}
|
||||||
let ret = false;
|
let ret = false;
|
||||||
for (const ignore of ignores) {
|
for (const ignore of ignores) {
|
||||||
if (typeof ignore === 'string' && file === ignore) {
|
if (typeof ignore === "string" && file === ignore) {
|
||||||
ret = true;
|
ret = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -233,20 +238,20 @@ export class Flyway {
|
|||||||
if (history.hash !== hash && this.allowHashNotMatch === false) {
|
if (history.hash !== hash && this.allowHashNotMatch === false) {
|
||||||
throw new Error(file + `hash conflict ,old: ${history.hash} != new: ${hash}`);
|
throw new Error(file + `hash conflict ,old: ${history.hash} != new: ${hash}`);
|
||||||
}
|
}
|
||||||
this.logger.info('[ midfly ] script<' + file + '> already executed');
|
this.logger.info("[ midfly ] script<" + file + "> already executed");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
this.logger.info('[ midfly ] script<' + file + '> not yet execute');
|
this.logger.info("[ midfly ] script<" + file + "> not yet execute");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getFileHash(filepath: string) {
|
private async getFileHash(filepath: string) {
|
||||||
const content = fs.readFileSync(filepath).toString();
|
const content = fs.readFileSync(filepath).toString();
|
||||||
return crypto.createHash('md5').update(content.toString()).digest('hex');
|
return crypto.createHash("md5").update(content.toString()).digest("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async execSql(filepath: string, queryRunner: QueryRunner) {
|
private async execSql(filepath: string, queryRunner: QueryRunner) {
|
||||||
this.logger.info('[ midfly ] exec ', filepath);
|
this.logger.info("[ midfly ] exec ", filepath);
|
||||||
const content = fs.readFileSync(filepath).toString().trim();
|
const content = fs.readFileSync(filepath).toString().trim();
|
||||||
const arr = this.splitSql2Array(content);
|
const arr = this.splitSql2Array(content);
|
||||||
for (const s of arr) {
|
for (const s of arr) {
|
||||||
@@ -255,11 +260,11 @@ export class Flyway {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async execOnePart(sql: string, queryRunner: QueryRunner) {
|
private async execOnePart(sql: string, queryRunner: QueryRunner) {
|
||||||
this.logger.debug('exec sql index: ', sql);
|
this.logger.debug("exec sql index: ", sql);
|
||||||
try {
|
try {
|
||||||
await queryRunner.query(sql);
|
await queryRunner.query(sql);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this.logger.error('exec sql error : ', err.message, err);
|
this.logger.error("exec sql error : ", err.message, err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,11 +280,11 @@ export class Flyway {
|
|||||||
|
|
||||||
const temp = String(str).trim();
|
const temp = String(str).trim();
|
||||||
|
|
||||||
if (temp === 'null') {
|
if (temp === "null") {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const semicolon = ';';
|
const semicolon = ";";
|
||||||
const deepChars = ['"', "'"];
|
const deepChars = ['"', "'"];
|
||||||
const splits = [];
|
const splits = [];
|
||||||
|
|
||||||
@@ -289,7 +294,7 @@ export class Flyway {
|
|||||||
|
|
||||||
if (deepChars.indexOf(charAt) >= 0) {
|
if (deepChars.indexOf(charAt) >= 0) {
|
||||||
//如果是深度char
|
//如果是深度char
|
||||||
if (i !== 0 && temp.charAt(i - 1) === '\\') {
|
if (i !== 0 && temp.charAt(i - 1) === "\\") {
|
||||||
//如果前一个是转义字符,忽略它
|
//如果前一个是转义字符,忽略它
|
||||||
} else {
|
} else {
|
||||||
//说明需要进出深度了
|
//说明需要进出深度了
|
||||||
|
|||||||
@@ -38,5 +38,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "a6ef6996c3a68ce0c2a4577a0934ed74a53f0515"
|
"gitHead": "268cd6cc9cb4f1f3d5d5d77859a82f18f0cb6db7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,5 +45,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "a6ef6996c3a68ce0c2a4577a0934ed74a53f0515"
|
"gitHead": "268cd6cc9cb4f1f3d5d5d77859a82f18f0cb6db7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { HttpClient, ILogger } from "@certd/basic";
|
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 punycode from "punycode.js";
|
||||||
import { CreateRecordOptions, DnsProviderContext, DnsProviderDefine, DnsResolveRecord, DomainRecord, IDnsProvider, RemoveRecordOptions } from "./api.js";
|
import { CreateRecordOptions, DnsProviderContext, DnsProviderDefine, DnsResolveRecord, DomainRecord, IDnsProvider, RemoveRecordOptions } from "./api.js";
|
||||||
import { dnsProviderRegistry } from "./registry.js";
|
import { dnsProviderRegistry } from "./registry.js";
|
||||||
@@ -7,13 +7,9 @@ export abstract class AbstractDnsProvider<T = any> implements IDnsProvider<T> {
|
|||||||
ctx!: DnsProviderContext;
|
ctx!: DnsProviderContext;
|
||||||
http!: HttpClient;
|
http!: HttpClient;
|
||||||
logger!: ILogger;
|
logger!: ILogger;
|
||||||
runtimeDepsService?: IRuntimeDepsService;
|
|
||||||
|
|
||||||
async importRuntime(specifier: string) {
|
async importRuntime(specifier: string) {
|
||||||
if (!this.runtimeDepsService) {
|
return await getRuntimeDepsService().importRuntime(specifier, this.logger);
|
||||||
throw new Error("runtimeDepsService 未初始化");
|
|
||||||
}
|
|
||||||
return await this.runtimeDepsService.importRuntime(specifier, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
usePunyCode(): boolean {
|
usePunyCode(): boolean {
|
||||||
@@ -42,9 +38,6 @@ export abstract class AbstractDnsProvider<T = any> implements IDnsProvider<T> {
|
|||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.logger = ctx.logger;
|
this.logger = ctx.logger;
|
||||||
this.http = ctx.http;
|
this.http = ctx.http;
|
||||||
if (!this.runtimeDepsService && this.ctx.serviceGetter) {
|
|
||||||
this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseDomain(fullDomain: string) {
|
async parseDomain(fullDomain: string) {
|
||||||
|
|||||||
@@ -59,7 +59,6 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"core-js": "^3.36.0",
|
"core-js": "^3.36.0",
|
||||||
"cos-js-sdk-v5": "^1.7.0",
|
|
||||||
"cron-parser": "^4.9.0",
|
"cron-parser": "^4.9.0",
|
||||||
"cropperjs": "^1.6.1",
|
"cropperjs": "^1.6.1",
|
||||||
"cssnano": "^7.0.6",
|
"cssnano": "^7.0.6",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
"@typescript-eslint/no-empty-function": "off",
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-this-alias": "off",
|
"@typescript-eslint/no-this-alias": "off",
|
||||||
// 允许any
|
"@typescript-eslint/no-unsafe-assignment": "off"
|
||||||
"@typescript-eslint/no-unsafe-anyassignment": "off"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"format": "prettier --write src",
|
"format": "prettier --write src",
|
||||||
"lint": "mwts fix",
|
"lint": "mwts fix",
|
||||||
"ci": "pnpm run cov",
|
"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-only": "cross-env NODE_ENV=production mwtsc -p tsconfig.build.json --cleanOutDir --skipLibCheck",
|
||||||
"build": "pnpm run build-only && pnpm run export-metadata",
|
"build": "pnpm run build-only && pnpm run export-metadata",
|
||||||
"export-metadata": "node export-plugin-yaml.js",
|
"export-metadata": "node export-plugin-yaml.js",
|
||||||
@@ -133,6 +134,7 @@
|
|||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
|
"eslint":"^7.32.0",
|
||||||
"typescript": "^5.4.2",
|
"typescript": "^5.4.2",
|
||||||
"why-is-node-running": "^3.2.2"
|
"why-is-node-running": "^3.2.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { getRuntimeDepsService } from "@certd/pipeline";
|
||||||
import { ALL, Body, Controller, Inject, Post, Provide, Query, RequestIP } from "@midwayjs/core";
|
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 { addonRegistry, AddonService, CrudController, SysPrivateSettings, SysPublicSettings, SysSafeSetting, SysSettingsEntity, SysSettingsService } from "@certd/lib-server";
|
||||||
import { cloneDeep, merge } from "lodash-es";
|
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 { http, logger, utils } from "@certd/basic";
|
||||||
import { CodeService } from "../../../modules/basic/service/code-service.js";
|
import { CodeService } from "../../../modules/basic/service/code-service.js";
|
||||||
import { SmsServiceFactory } from "../../../modules/basic/sms/factory.js";
|
import { SmsServiceFactory } from "../../../modules/basic/sms/factory.js";
|
||||||
import { RuntimeDepsService } from "../../../modules/runtime-deps/runtime-deps-service.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@@ -24,9 +24,6 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
|||||||
codeService: CodeService;
|
codeService: CodeService;
|
||||||
@Inject()
|
@Inject()
|
||||||
addonService: AddonService;
|
addonService: AddonService;
|
||||||
@Inject()
|
|
||||||
runtimeDepsService: RuntimeDepsService;
|
|
||||||
|
|
||||||
getService() {
|
getService() {
|
||||||
return this.service;
|
return this.service;
|
||||||
}
|
}
|
||||||
@@ -222,7 +219,7 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
|||||||
|
|
||||||
@Post("/clearRuntimeDeps", { description: "sys:settings:edit" })
|
@Post("/clearRuntimeDeps", { description: "sys:settings:edit" })
|
||||||
async clearRuntimeDeps() {
|
async clearRuntimeDeps() {
|
||||||
await this.runtimeDepsService.clearRuntimeDeps();
|
await getRuntimeDepsService().clearRuntimeDeps();
|
||||||
return this.ok(true);
|
return this.ok(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
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 { AutoInitSite } from "./auto-init-site.js";
|
||||||
import { AutoLoadPlugins } from "./auto-load-plugins.js";
|
import { AutoLoadPlugins } from "./auto-load-plugins.js";
|
||||||
import { AutoCron } from "./auto-cron.js";
|
|
||||||
import { AutoMitterRegister } from "./auto-mitter-register.js";
|
import { AutoMitterRegister } from "./auto-mitter-register.js";
|
||||||
import { AutoPipelineEmitterRegister } from "./auto-pipeline-emitter-register.js";
|
import { AutoPipelineEmitterRegister } from "./auto-pipeline-emitter-register.js";
|
||||||
import { AutoFix } from "./fix/auto-fix.js";
|
|
||||||
import { AutoPrint } from "./auto-print.js";
|
import { AutoPrint } from "./auto-print.js";
|
||||||
|
import { AutoFix } from "./fix/auto-fix.js";
|
||||||
|
|
||||||
@Autoload()
|
@Autoload()
|
||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
|||||||
@@ -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 { logger } from "@certd/basic";
|
||||||
import { PluginService } from "../plugin/service/plugin-service.js";
|
import { PluginService } from "../plugin/service/plugin-service.js";
|
||||||
import { registerPaymentProviders } from "../suite/payments/index.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()
|
@Provide()
|
||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
@@ -10,8 +13,8 @@ export class AutoLoadPlugins {
|
|||||||
@Inject()
|
@Inject()
|
||||||
pluginService: PluginService;
|
pluginService: PluginService;
|
||||||
|
|
||||||
@Inject()
|
@Config("runtimeDeps")
|
||||||
runtimeDepsService: RuntimeDepsService;
|
runtimeDepsConfig: any;
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
logger.info(`加载插件开始,加载模式:${process.env.certd_plugin_loadmode}`);
|
logger.info(`加载插件开始,加载模式:${process.env.certd_plugin_loadmode}`);
|
||||||
@@ -35,7 +38,16 @@ export class AutoLoadPlugins {
|
|||||||
await registerPaymentProviders();
|
await registerPaymentProviders();
|
||||||
logger.info(`加载插件完成,加载模式:${process.env.certd_plugin_loadmode}`);
|
logger.info(`加载插件完成,加载模式:${process.env.certd_plugin_loadmode}`);
|
||||||
|
|
||||||
|
//初始化第三方依赖服务
|
||||||
|
initRuntimeDepsService(this.runtimeDepsConfig, {
|
||||||
|
pluginRegistry,
|
||||||
|
accessRegistry,
|
||||||
|
notificationRegistry,
|
||||||
|
dnsProviderRegistry,
|
||||||
|
addonRegistry,
|
||||||
|
});
|
||||||
// 收集插件 dependPackages 并安装
|
// 收集插件 dependPackages 并安装
|
||||||
await this.runtimeDepsService.refreshPluginDeps();
|
const service = getRuntimeDepsService();
|
||||||
|
service.refreshPluginDeps();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { AccessService, AccessSysGetter, CodeErrorException, SysSettingsService } from "@certd/lib-server";
|
||||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
import { ISmsService } from "../sms/api.js";
|
import { ISmsService } from "../sms/api.js";
|
||||||
@@ -6,7 +6,6 @@ import { SmsServiceFactory } from "../sms/factory.js";
|
|||||||
import { CaptchaService } from "./captcha-service.js";
|
import { CaptchaService } from "./captcha-service.js";
|
||||||
import { EmailService } from "./email-service.js";
|
import { EmailService } from "./email-service.js";
|
||||||
import { CaptchaRequest } from "../../../plugins/plugin-captcha/api.js";
|
import { CaptchaRequest } from "../../../plugins/plugin-captcha/api.js";
|
||||||
import { RuntimeDepsService } from "../../runtime-deps/runtime-deps-service.js";
|
|
||||||
|
|
||||||
// {data: '<svg.../svg>', text: 'abcd'}
|
// {data: '<svg.../svg>', text: 'abcd'}
|
||||||
/**
|
/**
|
||||||
@@ -25,9 +24,6 @@ export class CodeService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
captchaService: CaptchaService;
|
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 });
|
return await this.captchaService.doValidate({ form: body, req });
|
||||||
}
|
}
|
||||||
@@ -60,7 +56,6 @@ export class CodeService {
|
|||||||
await sender.setCtx({
|
await sender.setCtx({
|
||||||
accessService: accessGetter,
|
accessService: accessGetter,
|
||||||
config: smsConfig,
|
config: smsConfig,
|
||||||
runtimeDepsService: this.runtimeDepsService,
|
|
||||||
});
|
});
|
||||||
const smsCode = randomNumber(verificationCodeLength);
|
const smsCode = randomNumber(verificationCodeLength);
|
||||||
await sender.sendSmsCode({
|
await sender.sendSmsCode({
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { FormItemProps, IAccessService } from "@certd/pipeline";
|
import { FormItemProps, IAccessService } from "@certd/pipeline";
|
||||||
import type { RuntimeDepsService } from "../../runtime-deps/runtime-deps-service.js";
|
|
||||||
|
|
||||||
export interface ISmsService {
|
export interface ISmsService {
|
||||||
sendSmsCode(opts: { mobile: string; code: string; phoneCode: string }): Promise<void>;
|
sendSmsCode(opts: { mobile: string; code: string; phoneCode: string }): Promise<void>;
|
||||||
setCtx(ctx: { accessService: IAccessService; config: { [key: string]: any }; runtimeDepsService?: RuntimeDepsService }): Promise<void>;
|
setCtx(ctx: { accessService: IAccessService; config: { [key: string]: any } }): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PluginInputs<T = any> = {
|
export type PluginInputs<T = any> = {
|
||||||
@@ -13,5 +12,4 @@ export type PluginInputs<T = any> = {
|
|||||||
export type SmsPluginCtx<T = any> = {
|
export type SmsPluginCtx<T = any> = {
|
||||||
accessService: IAccessService;
|
accessService: IAccessService;
|
||||||
config: T;
|
config: T;
|
||||||
runtimeDepsService?: RuntimeDepsService;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { TencentAccess } from "../../../plugins/plugin-lib/tencent/access.js";
|
import { TencentAccess } from "../../../plugins/plugin-lib/tencent/access.js";
|
||||||
import { ISmsService, PluginInputs, SmsPluginCtx } from "./api.js";
|
import { importRuntime } from "@certd/pipeline";
|
||||||
|
import { ISmsService, PluginInputs } from "./api.js";
|
||||||
|
|
||||||
export type TencentSmsConfig = {
|
export type TencentSmsConfig = {
|
||||||
accessId: string;
|
accessId: string;
|
||||||
@@ -66,26 +67,16 @@ export class TencentSmsService implements ISmsService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx: SmsPluginCtx<TencentSmsConfig>;
|
ctx: { accessService: any; config: TencentSmsConfig };
|
||||||
|
|
||||||
async setCtx(ctx: any) {
|
async setCtx(ctx: any) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
if (this.ctx.runtimeDepsService) {
|
|
||||||
await this.ctx.runtimeDepsService.ensureDependencies({
|
|
||||||
dependencies: {
|
|
||||||
"tencentcloud-sdk-nodejs": "^4.1.112",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getClient() {
|
async getClient() {
|
||||||
if (!this.ctx.runtimeDepsService) {
|
const sdk = await importRuntime("tencentcloud-sdk-nodejs/tencentcloud/services/sms/v20210111/index.js");
|
||||||
throw new Error("动态依赖服务未初始化,无法加载腾讯云短信SDK");
|
|
||||||
}
|
|
||||||
const sdk = await this.ctx.runtimeDepsService.importRuntime("tencentcloud-sdk-nodejs/tencentcloud/services/sms/v20210111/index.js");
|
|
||||||
const client = sdk.v20210111.Client;
|
const client = sdk.v20210111.Client;
|
||||||
const access = await this.ctx.accessService.getById<TencentAccess>(this.ctx.config.accessId);
|
const access: TencentAccess = await this.ctx.accessService.getById(this.ctx.config.accessId);
|
||||||
|
|
||||||
// const region = this.region;
|
// const region = this.region;
|
||||||
const clientConfig = {
|
const clientConfig = {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { importRuntime } from "@certd/pipeline";
|
||||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
import { UserSettingsService } from "./user-settings-service.js";
|
import { UserSettingsService } from "./user-settings-service.js";
|
||||||
import { UserTwoFactorSetting } from "./models.js";
|
import { UserTwoFactorSetting } from "./models.js";
|
||||||
@@ -13,13 +14,12 @@ export class TwoFactorService {
|
|||||||
userSettingsService: UserSettingsService;
|
userSettingsService: UserSettingsService;
|
||||||
@Inject()
|
@Inject()
|
||||||
userService: UserService;
|
userService: UserService;
|
||||||
|
|
||||||
async getAuthenticatorQrCode(userId: any) {
|
async getAuthenticatorQrCode(userId: any) {
|
||||||
const setting = await this.getSetting(userId);
|
const setting = await this.getSetting(userId);
|
||||||
|
|
||||||
const authenticatorSetting = setting.authenticator;
|
const authenticatorSetting = setting.authenticator;
|
||||||
if (!authenticatorSetting.secret) {
|
if (!authenticatorSetting.secret) {
|
||||||
const { authenticator } = await import("otplib");
|
const { authenticator } = await importRuntime("otplib");
|
||||||
|
|
||||||
authenticatorSetting.secret = authenticator.generateSecret();
|
authenticatorSetting.secret = authenticator.generateSecret();
|
||||||
await this.userSettingsService.saveSetting(userId, null, setting);
|
await this.userSettingsService.saveSetting(userId, null, setting);
|
||||||
@@ -38,7 +38,7 @@ export class TwoFactorService {
|
|||||||
|
|
||||||
async saveAuthenticator(req: { userId: any; verifyCode: any }) {
|
async saveAuthenticator(req: { userId: any; verifyCode: any }) {
|
||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const { authenticator } = await import("otplib");
|
const { authenticator } = await importRuntime("otplib");
|
||||||
const setting = await this.getSetting(userId);
|
const setting = await this.getSetting(userId);
|
||||||
|
|
||||||
const authenticatorSetting = setting.authenticator;
|
const authenticatorSetting = setting.authenticator;
|
||||||
@@ -77,7 +77,7 @@ export class TwoFactorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async verifyAuthenticatorCode(userId: any, verifyCode: string) {
|
async verifyAuthenticatorCode(userId: any, verifyCode: string) {
|
||||||
const { authenticator } = await import("otplib");
|
const { authenticator } = await importRuntime("otplib");
|
||||||
const setting = await this.getSetting(userId);
|
const setting = await this.getSetting(userId);
|
||||||
if (!setting.authenticator.enabled) {
|
if (!setting.authenticator.enabled) {
|
||||||
throw new Error("authenticator 未开启");
|
throw new Error("authenticator 未开启");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IServiceGetter } from "@certd/pipeline";
|
import { IServiceGetter } from "@certd/pipeline";
|
||||||
import { ApplicationContext, IMidwayContainer, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
import { ApplicationContext, IMidwayContainer, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
import { AccessGetter, AccessService } from "@certd/lib-server";
|
import { AccessGetter, AccessService } from "@certd/lib-server";
|
||||||
import { CnameProxyService } from "./cname-proxy-service.js";
|
import { CnameProxyService } from "./cname-proxy-service.js";
|
||||||
@@ -13,7 +13,6 @@ import { CertInfoGetter } from "./cert-info-getter.js";
|
|||||||
import { CertInfoService } from "../../../monitor/index.js";
|
import { CertInfoService } from "../../../monitor/index.js";
|
||||||
import { ICertInfoGetter } from "@certd/plugin-lib";
|
import { ICertInfoGetter } from "@certd/plugin-lib";
|
||||||
import { CnameProviderService } from "../../../cname/service/cname-provider-service.js";
|
import { CnameProviderService } from "../../../cname/service/cname-provider-service.js";
|
||||||
import { RuntimeDepsService } from "../../../runtime-deps/runtime-deps-service.js";
|
|
||||||
|
|
||||||
const serviceNames = ["ocrService"];
|
const serviceNames = ["ocrService"];
|
||||||
export class TaskServiceGetter implements IServiceGetter {
|
export class TaskServiceGetter implements IServiceGetter {
|
||||||
@@ -39,8 +38,6 @@ export class TaskServiceGetter implements IServiceGetter {
|
|||||||
return (await this.getDomainVerifierGetter()) as T;
|
return (await this.getDomainVerifierGetter()) as T;
|
||||||
} else if (serviceName === "certInfoGetter") {
|
} else if (serviceName === "certInfoGetter") {
|
||||||
return (await this.getCertInfoGetter()) as T;
|
return (await this.getCertInfoGetter()) as T;
|
||||||
} else if (serviceName === "runtimeDepsService") {
|
|
||||||
return (await this.getRuntimeDepsService()) as T;
|
|
||||||
} else {
|
} else {
|
||||||
if (!serviceNames.includes(serviceName)) {
|
if (!serviceNames.includes(serviceName)) {
|
||||||
throw new Error(`${serviceName} not in whitelist`);
|
throw new Error(`${serviceName} not in whitelist`);
|
||||||
@@ -84,10 +81,6 @@ export class TaskServiceGetter implements IServiceGetter {
|
|||||||
const domainService: DomainService = await this.appCtx.getAsync("domainService");
|
const domainService: DomainService = await this.appCtx.getAsync("domainService");
|
||||||
return new DomainVerifierGetter(this.userId, this.projectId, domainService);
|
return new DomainVerifierGetter(this.userId, this.projectId, domainService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRuntimeDepsService(): Promise<RuntimeDepsService> {
|
|
||||||
return await this.appCtx.getAsync("runtimeDepsService");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@Provide()
|
@Provide()
|
||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
|||||||
@@ -962,7 +962,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async batchDelete(ids: number[], userId?: number, projectId?: number):Promise<number> {
|
async batchDelete(ids: number[], userId?: number, projectId?: number): Promise<number> {
|
||||||
if (!isPlus()) {
|
if (!isPlus()) {
|
||||||
throw new NeedVIPException("此功能需要升级Certd专业版");
|
throw new NeedVIPException("此功能需要升级Certd专业版");
|
||||||
}
|
}
|
||||||
@@ -976,7 +976,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
await this.delete(id);
|
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) {
|
async batchUpdateGroup(ids: number[], groupId: number, userId: any, projectId?: number) {
|
||||||
|
|||||||
@@ -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 { addonRegistry, BaseService, PageReq } from "@certd/lib-server";
|
||||||
import { PluginEntity } from "../entity/plugin.js";
|
import { PluginEntity } from "../entity/plugin.js";
|
||||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||||
@@ -6,14 +6,13 @@ import { IsNull, Not, Repository } from "typeorm";
|
|||||||
import { isComm } from "@certd/plus-core";
|
import { isComm } from "@certd/plus-core";
|
||||||
import { BuiltInPluginService } from "../../pipeline/service/builtin-plugin-service.js";
|
import { BuiltInPluginService } from "../../pipeline/service/builtin-plugin-service.js";
|
||||||
import { merge } from "lodash-es";
|
import { merge } from "lodash-es";
|
||||||
import { accessRegistry, notificationRegistry, pluginRegistry } from "@certd/pipeline";
|
|
||||||
import { dnsProviderRegistry } from "@certd/plugin-cert";
|
import { dnsProviderRegistry } from "@certd/plugin-cert";
|
||||||
import { logger } from "@certd/basic";
|
import { logger } from "@certd/basic";
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import { getDefaultAccessPlugin, getDefaultDeployPlugin, getDefaultDnsPlugin } from "./default-plugin.js";
|
import { getDefaultAccessPlugin, getDefaultDeployPlugin, getDefaultDnsPlugin } from "./default-plugin.js";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
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 = {
|
export type PluginImportReq = {
|
||||||
content: string;
|
content: string;
|
||||||
@@ -49,9 +48,6 @@ export class PluginService extends BaseService<PluginEntity> {
|
|||||||
@Inject()
|
@Inject()
|
||||||
builtInPluginService: BuiltInPluginService;
|
builtInPluginService: BuiltInPluginService;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
runtimeDepsService: RuntimeDepsService;
|
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
getRepository() {
|
getRepository() {
|
||||||
return this.repository;
|
return this.repository;
|
||||||
@@ -270,7 +266,7 @@ export class PluginService extends BaseService<PluginEntity> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.registerPlugin(item);
|
await this.registerPlugin(item);
|
||||||
await this.runtimeDepsService.refreshPluginDeps();
|
await this.refreshPluginDeps();
|
||||||
}
|
}
|
||||||
|
|
||||||
async unRegisterById(id: any) {
|
async unRegisterById(id: any) {
|
||||||
@@ -298,7 +294,12 @@ export class PluginService extends BaseService<PluginEntity> {
|
|||||||
} else {
|
} else {
|
||||||
logger.warn(`不支持的插件类型:${item.pluginType}`);
|
logger.warn(`不支持的插件类型:${item.pluginType}`);
|
||||||
}
|
}
|
||||||
await this.runtimeDepsService.refreshPluginDeps();
|
await this.refreshPluginDeps();
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshPluginDeps() {
|
||||||
|
const service = getRuntimeDepsService();
|
||||||
|
service.refreshPluginDeps();
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(param: any) {
|
async update(param: any) {
|
||||||
@@ -334,7 +335,7 @@ export class PluginService extends BaseService<PluginEntity> {
|
|||||||
if (!isBareModuleSpecifier(modulePath)) {
|
if (!isBareModuleSpecifier(modulePath)) {
|
||||||
return await importLocalModule(modulePath);
|
return await importLocalModule(modulePath);
|
||||||
}
|
}
|
||||||
return await this.runtimeDepsService.importRuntime(modulePath, logger);
|
return await importRuntimeDirect(modulePath, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getPluginClassFromFile(item: any) {
|
private async getPluginClassFromFile(item: any) {
|
||||||
|
|||||||
@@ -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<string> {
|
|
||||||
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<RegistryProbeResult> {
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -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 { AliyunAccess } from "../../plugin-lib/aliyun/access/aliyun-access.js";
|
||||||
import { AliyunClient } from "../../plugin-lib/aliyun/index.js";
|
import { AliyunClient } from "../../plugin-lib/aliyun/index.js";
|
||||||
import { Pager, PageRes, PageSearch } from "@certd/pipeline";
|
import { Pager, PageRes, PageSearch } from "@certd/pipeline";
|
||||||
@@ -16,7 +16,7 @@ export class AliyunDnsProvider extends AbstractDnsProvider {
|
|||||||
async onInstance() {
|
async onInstance() {
|
||||||
const access: AliyunAccess = this.ctx.access as AliyunAccess;
|
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({
|
await this.client.init({
|
||||||
accessKeyId: access.accessKeyId,
|
accessKeyId: access.accessKeyId,
|
||||||
accessKeySecret: access.accessKeySecret,
|
accessKeySecret: access.accessKeySecret,
|
||||||
|
|||||||
@@ -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 { utils } from "@certd/basic";
|
||||||
|
|
||||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||||
@@ -241,7 +241,7 @@ export class DeployCertToAliyunAckPlugin extends AbstractTaskPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getClient(aliyunProvider: any, regionId: string) {
|
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({
|
await client.init({
|
||||||
accessKeyId: aliyunProvider.accessKeyId,
|
accessKeyId: aliyunProvider.accessKeyId,
|
||||||
accessKeySecret: aliyunProvider.accessKeySecret,
|
accessKeySecret: aliyunProvider.accessKeySecret,
|
||||||
|
|||||||
@@ -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 { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
|
||||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||||
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
|
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
|
||||||
@@ -128,7 +128,7 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
|
|||||||
async onInstance() {}
|
async onInstance() {}
|
||||||
|
|
||||||
async getLBClient(access: AliyunAccess, region: string) {
|
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";
|
const version = "2020-06-16";
|
||||||
await client.init({
|
await client.init({
|
||||||
|
|||||||
@@ -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 { AbstractTaskPlugin, CertTargetItem, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
|
||||||
import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert";
|
import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert";
|
||||||
import { CertInfo, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
import { CertInfo, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||||
@@ -209,7 +209,7 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getClient(access: AliyunAccess) {
|
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({
|
await client.init({
|
||||||
accessKeyId: access.accessKeyId,
|
accessKeyId: access.accessKeyId,
|
||||||
accessKeySecret: access.accessKeySecret,
|
accessKeySecret: access.accessKeySecret,
|
||||||
|
|||||||
@@ -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 { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
|
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
|
||||||
@@ -143,7 +143,7 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getClient(access: AliyunAccess) {
|
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({
|
await client.init({
|
||||||
accessKeyId: access.accessKeyId,
|
accessKeyId: access.accessKeyId,
|
||||||
accessKeySecret: access.accessKeySecret,
|
accessKeySecret: access.accessKeySecret,
|
||||||
|
|||||||
@@ -137,8 +137,8 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
|
|||||||
|
|
||||||
const client = await this.getClient(access);
|
const client = await this.getClient(access);
|
||||||
|
|
||||||
const $Util = await access.importRuntime("@alicloud/tea-util");
|
const $Util = await this.importRuntime("@alicloud/tea-util");
|
||||||
const $OpenApi = await access.importRuntime("@alicloud/openapi-client");
|
const $OpenApi = await this.importRuntime("@alicloud/openapi-client");
|
||||||
|
|
||||||
let privateKey = this.cert.key;
|
let privateKey = this.cert.key;
|
||||||
try {
|
try {
|
||||||
@@ -204,7 +204,7 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getClient(access: AliyunAccess) {
|
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({
|
const config = new $OpenApi.Config({
|
||||||
accessKeyId: access.accessKeyId,
|
accessKeyId: access.accessKeyId,
|
||||||
accessKeySecret: access.accessKeySecret,
|
accessKeySecret: access.accessKeySecret,
|
||||||
@@ -221,8 +221,8 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
|
|||||||
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
||||||
const client = await this.getClient(access);
|
const client = await this.getClient(access);
|
||||||
|
|
||||||
const $OpenApi = await access.importRuntime("@alicloud/openapi-client");
|
const $OpenApi = await this.importRuntime("@alicloud/openapi-client");
|
||||||
const $Util = await access.importRuntime("@alicloud/tea-util");
|
const $Util = await this.importRuntime("@alicloud/tea-util");
|
||||||
const params = new $OpenApi.Params({
|
const params = new $OpenApi.Params({
|
||||||
// 接口名称
|
// 接口名称
|
||||||
action: "ListCustomDomains",
|
action: "ListCustomDomains",
|
||||||
|
|||||||
@@ -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 { CertInfo, CertReader } from "@certd/plugin-cert";
|
||||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||||
import { CertApplyPluginNames } from "@certd/plugin-cert";
|
import { CertApplyPluginNames } from "@certd/plugin-cert";
|
||||||
@@ -117,7 +117,7 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
|
|||||||
async onInstance() {}
|
async onInstance() {}
|
||||||
|
|
||||||
async getLBClient(access: AliyunAccess, region: string) {
|
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";
|
const version = "2022-04-30";
|
||||||
await client.init({
|
await client.init({
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
|
|||||||
|
|
||||||
async getClient(access: AliyunAccess) {
|
async getClient(access: AliyunAccess) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const OSS = await access.importRuntime("ali-oss");
|
const OSS = await this.importRuntime("ali-oss");
|
||||||
return new OSS.default({
|
return new OSS.default({
|
||||||
accessKeyId: access.accessKeyId,
|
accessKeyId: access.accessKeyId,
|
||||||
accessKeySecret: access.accessKeySecret,
|
accessKeySecret: access.accessKeySecret,
|
||||||
|
|||||||
@@ -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 { CertInfo } from "@certd/plugin-cert";
|
||||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||||
import { CertApplyPluginNames } from "@certd/plugin-cert";
|
import { CertApplyPluginNames } from "@certd/plugin-cert";
|
||||||
@@ -130,7 +130,7 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
|
|||||||
async onInstance() {}
|
async onInstance() {}
|
||||||
|
|
||||||
async getLBClient(access: AliyunAccess, region: string) {
|
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";
|
const version = "2014-05-15";
|
||||||
await client.init({
|
await client.init({
|
||||||
accessKeyId: access.accessKeyId,
|
accessKeyId: access.accessKeyId,
|
||||||
|
|||||||
+2
-2
@@ -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 { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
|
||||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||||
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
|
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
|
||||||
@@ -103,7 +103,7 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
|
|||||||
async onInstance() {}
|
async onInstance() {}
|
||||||
|
|
||||||
async getWafClient(access: AliyunAccess) {
|
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({
|
await client.init({
|
||||||
accessKeyId: access.accessKeyId,
|
accessKeyId: access.accessKeyId,
|
||||||
accessKeySecret: access.accessKeySecret,
|
accessKeySecret: access.accessKeySecret,
|
||||||
|
|||||||
+2
-2
@@ -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 { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
|
||||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||||
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
|
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
|
||||||
@@ -115,7 +115,7 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
|
|||||||
async onInstance() {}
|
async onInstance() {}
|
||||||
|
|
||||||
async getWafClient(access: AliyunAccess) {
|
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({
|
await client.init({
|
||||||
accessKeyId: access.accessKeyId,
|
accessKeyId: access.accessKeyId,
|
||||||
accessKeySecret: access.accessKeySecret,
|
accessKeySecret: access.accessKeySecret,
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export class AliossAccess extends BaseAccess {
|
|||||||
|
|
||||||
async getClient(access: AliyunAccess) {
|
async getClient(access: AliyunAccess) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const OSS = await access.importRuntime("ali-oss");
|
const OSS = await this.importRuntime("ali-oss");
|
||||||
return new OSS.default({
|
return new OSS.default({
|
||||||
accessKeyId: access.accessKeyId,
|
accessKeyId: access.accessKeyId,
|
||||||
accessKeySecret: access.accessKeySecret,
|
accessKeySecret: access.accessKeySecret,
|
||||||
|
|||||||
@@ -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 { AliyunClientV2 } from "../lib/aliyun-client-v2.js";
|
||||||
import { AliyunSslClient } from "../lib/ssl-client.js";
|
import { AliyunSslClient } from "../lib/ssl-client.js";
|
||||||
@IsAccess({
|
@IsAccess({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ILogger } from "@certd/basic";
|
import { ILogger } from "@certd/basic";
|
||||||
import { AliyunAccess } from "../access/aliyun-access.js";
|
import { AliyunAccess } from "../access/aliyun-access.js";
|
||||||
|
import { importRuntime as importRuntimeDirect } from "@certd/pipeline";
|
||||||
|
|
||||||
export type AliyunClientV2Req = {
|
export type AliyunClientV2Req = {
|
||||||
action: string;
|
action: string;
|
||||||
@@ -26,11 +27,15 @@ export class AliyunClientV2 {
|
|||||||
this.endpoint = opts.endpoint;
|
this.endpoint = opts.endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async importRuntime(name: string) {
|
||||||
|
return await importRuntimeDirect(name, this.logger);
|
||||||
|
}
|
||||||
|
|
||||||
async getClient() {
|
async getClient() {
|
||||||
if (this.client) {
|
if (this.client) {
|
||||||
return 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");
|
// const Credential = await import("@alicloud/credentials");
|
||||||
// //@ts-ignore
|
// //@ts-ignore
|
||||||
// const credential = new Credential.default.default({
|
// const credential = new Credential.default.default({
|
||||||
@@ -52,9 +57,9 @@ export class AliyunClientV2 {
|
|||||||
async doRequest(req: AliyunClientV2Req) {
|
async doRequest(req: AliyunClientV2Req) {
|
||||||
const client = await this.getClient();
|
const client = await this.getClient();
|
||||||
|
|
||||||
const $OpenApi = await this.access.importRuntime("@alicloud/openapi-client");
|
const $OpenApi = await this.importRuntime("@alicloud/openapi-client");
|
||||||
const $Util = await this.access.importRuntime("@alicloud/tea-util");
|
const $Util = await this.importRuntime("@alicloud/tea-util");
|
||||||
const OpenApiUtil = await this.access.importRuntime("@alicloud/openapi-util");
|
const OpenApiUtil = await this.importRuntime("@alicloud/openapi-util");
|
||||||
const params = new $OpenApi.Params({
|
const params = new $OpenApi.Params({
|
||||||
// 接口名称
|
// 接口名称
|
||||||
action: req.action,
|
action: req.action,
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
import { getGlobalAgents, ILogger } from "@certd/basic";
|
import { getGlobalAgents, ILogger } from "@certd/basic";
|
||||||
import { ImportRuntime } from "@certd/pipeline";
|
import { importRuntime as importRuntimeDirect } from "@certd/pipeline";
|
||||||
|
|
||||||
export class AliyunClient {
|
export class AliyunClient {
|
||||||
client: any;
|
client: any;
|
||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
agent: any;
|
agent: any;
|
||||||
useROAClient: boolean;
|
useROAClient: boolean;
|
||||||
importRuntime: ImportRuntime;
|
|
||||||
|
|
||||||
constructor(opts: { logger: ILogger; useROAClient?: boolean; importRuntime?: ImportRuntime }) {
|
constructor(opts: { logger: ILogger; useROAClient?: boolean }) {
|
||||||
this.logger = opts.logger;
|
this.logger = opts.logger;
|
||||||
this.useROAClient = opts.useROAClient || false;
|
this.useROAClient = opts.useROAClient || false;
|
||||||
this.importRuntime = opts.importRuntime || (async (specifier: string) => await import(specifier));
|
|
||||||
const agents = getGlobalAgents();
|
const agents = getGlobalAgents();
|
||||||
this.agent = agents.httpsAgent;
|
this.agent = agents.httpsAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async importRuntime(specifier: string) {
|
||||||
|
return await importRuntimeDirect(specifier, this.logger);
|
||||||
|
}
|
||||||
|
|
||||||
async getSdk() {
|
async getSdk() {
|
||||||
if (this.useROAClient) {
|
if (this.useROAClient) {
|
||||||
return await this.getROAClient();
|
return await this.getROAClient();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ILogger, utils } from "@certd/basic";
|
import { ILogger, utils } from "@certd/basic";
|
||||||
import { AliyunAccess } from "../access/index.js";
|
import { AliyunAccess } from "../access/index.js";
|
||||||
import { AliyunClient } from "./index.js";
|
import { AliyunClient } from "./index.js";
|
||||||
import { CertInfo, CertReader, SimpleCertDetail } from "@certd/plugin-lib";
|
import { CertInfo, CertReader, SimpleCertDetail } from "@certd/plugin-lib";
|
||||||
@@ -42,6 +42,7 @@ export type CasCertId = {
|
|||||||
export class AliyunSslClient {
|
export class AliyunSslClient {
|
||||||
opts: AliyunSslClientOpts;
|
opts: AliyunSslClientOpts;
|
||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
|
|
||||||
constructor(opts: AliyunSslClientOpts) {
|
constructor(opts: AliyunSslClientOpts) {
|
||||||
this.opts = opts;
|
this.opts = opts;
|
||||||
this.logger = opts.logger;
|
this.logger = opts.logger;
|
||||||
@@ -55,7 +56,7 @@ export class AliyunSslClient {
|
|||||||
|
|
||||||
async getClient() {
|
async getClient() {
|
||||||
const access = this.opts.access;
|
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";
|
let endpoint = this.opts.endpoint || "cas.aliyuncs.com";
|
||||||
if (this.opts.endpoint == null && this.opts.region) {
|
if (this.opts.endpoint == null && this.opts.region) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { TencentAccess, TencentCosAccess, TencentCosClient } from "../../tencent/index.js";
|
import { TencentAccess, TencentCosAccess, TencentCosClient } from "../../tencent/index.js";
|
||||||
import { BaseOssClient, OssFileItem } from "../api.js";
|
import { BaseOssClient, OssFileItem } from "../api.js";
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export class TencentAccess extends BaseAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getStsClient() {
|
async getStsClient() {
|
||||||
const sdk = await (this as any).importRuntime("tencentcloud-sdk-nodejs/tencentcloud/services/sts/v20180813/index.js");
|
const sdk = await this.importRuntime("tencentcloud-sdk-nodejs/tencentcloud/services/sts/v20180813/index.js");
|
||||||
const StsClient = sdk.v20180813.Client;
|
const StsClient = sdk.v20180813.Client;
|
||||||
|
|
||||||
const clientConfig = {
|
const clientConfig = {
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import { TencentAccess } from "../access.js";
|
import { TencentAccess } from "../access.js";
|
||||||
import { ILogger, safePromise } from "@certd/basic";
|
import { ILogger, safePromise } from "@certd/basic";
|
||||||
import { ImportRuntime } from "@certd/pipeline";
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import { importRuntime as importRuntimeDirect } from "@certd/pipeline";
|
||||||
|
|
||||||
export class TencentCosClient {
|
export class TencentCosClient {
|
||||||
access: TencentAccess;
|
access: TencentAccess;
|
||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
region: string;
|
region: string;
|
||||||
bucket: string;
|
bucket: string;
|
||||||
importRuntime: ImportRuntime;
|
|
||||||
|
|
||||||
constructor(opts: { access: TencentAccess; logger: ILogger; region: string; bucket: string; importRuntime?: ImportRuntime }) {
|
constructor(opts: { access: TencentAccess; logger: ILogger; region: string; bucket: string }) {
|
||||||
this.access = opts.access;
|
this.access = opts.access;
|
||||||
this.logger = opts.logger;
|
this.logger = opts.logger;
|
||||||
this.bucket = opts.bucket;
|
this.bucket = opts.bucket;
|
||||||
this.region = opts.region;
|
this.region = opts.region;
|
||||||
this.importRuntime = opts.importRuntime || (async (specifier: string) => await import(specifier));
|
}
|
||||||
|
|
||||||
|
async importRuntime(specifier: string) {
|
||||||
|
return await importRuntimeDirect(specifier, this.logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCosClient() {
|
async getCosClient() {
|
||||||
|
|||||||
-348
@@ -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(", ")}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
-344
@@ -1,344 +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 { BaseAccess } = await import("@certd/pipeline");
|
|
||||||
const httpsModule = await import("node:https");
|
|
||||||
const { URL } = await import("node:url");
|
|
||||||
const axiosModule = await import("axios");
|
|
||||||
const formDataModule = await import("form-data");
|
|
||||||
const { authenticator } = await import("otplib");
|
|
||||||
|
|
||||||
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.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 {
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -142,7 +142,7 @@ export class DeployCertToTencentCosPlugin extends AbstractTaskPlugin {
|
|||||||
async onGetDomainList(data: any) {
|
async onGetDomainList(data: any) {
|
||||||
const access = await this.getAccess(this.accessId);
|
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({
|
const cos = new cosv5.default({
|
||||||
SecretId: access.secretId,
|
SecretId: access.secretId,
|
||||||
SecretKey: access.secretKey,
|
SecretKey: access.secretKey,
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export class TencentDeployCertToLive extends AbstractTaskPlugin {
|
|||||||
|
|
||||||
async getLiveClient() {
|
async getLiveClient() {
|
||||||
const accessProvider = await this.getAccess<TencentAccess>(this.accessId);
|
const accessProvider = await this.getAccess<TencentAccess>(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 CssClient = sdk.v20180801.Client;
|
||||||
|
|
||||||
const clientConfig = {
|
const clientConfig = {
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ export class UploadCertToTencent extends AbstractTaskPlugin {
|
|||||||
|
|
||||||
Client: any;
|
Client: any;
|
||||||
async onInstance() {
|
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;
|
this.Client = sdk.v20191205.Client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,15 @@ export class VolcengineCdnClient {
|
|||||||
this.opts = opts;
|
this.opts = opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async importRuntime(packageName: string) {
|
||||||
|
return this.opts.access.importRuntime(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
async getCdnClient() {
|
async getCdnClient() {
|
||||||
if (this.service) {
|
if (this.service) {
|
||||||
return this.service;
|
return this.service;
|
||||||
}
|
}
|
||||||
const importRuntime = this.opts.importRuntime || this.opts.access.importRuntime.bind(this.opts.access);
|
const { cdn } = await this.importRuntime("@volcengine/openapi");
|
||||||
const { cdn } = await importRuntime("@volcengine/openapi");
|
|
||||||
const service = new cdn.CdnService();
|
const service = new cdn.CdnService();
|
||||||
// 设置ak、sk
|
// 设置ak、sk
|
||||||
service.setAccessKeyId(this.opts.access.accessKeyId);
|
service.setAccessKeyId(this.opts.access.accessKeyId);
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ export class VolcengineDnsClient {
|
|||||||
constructor(opts: VolcengineOpts) {
|
constructor(opts: VolcengineOpts) {
|
||||||
this.opts = opts;
|
this.opts = opts;
|
||||||
}
|
}
|
||||||
|
async importRuntime(packageName: string) {
|
||||||
|
return this.opts.access.importRuntime(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
async doRequest(req: VolcengineReq) {
|
async doRequest(req: VolcengineReq) {
|
||||||
const importRuntime = this.opts.importRuntime || this.opts.access.importRuntime.bind(this.opts.access);
|
const { Signer } = await this.importRuntime("@volcengine/openapi");
|
||||||
const { Signer } = await importRuntime("@volcengine/openapi");
|
|
||||||
|
|
||||||
// http request data
|
// http request data
|
||||||
const openApiRequestData: any = {
|
const openApiRequestData: any = {
|
||||||
|
|||||||
+1
-1
@@ -243,7 +243,7 @@ export class VolcengineDeployToVOD extends AbstractTaskPlugin {
|
|||||||
return {
|
return {
|
||||||
value: item.Domain,
|
value: item.Domain,
|
||||||
label: item.Domain,
|
label: item.Domain,
|
||||||
domain : item.Domain,
|
domain: item.Domain,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return this.ctx.utils.options.buildGroupOptions(list, this.certDomains);
|
return this.ctx.utils.options.buildGroupOptions(list, this.certDomains);
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { VolcengineAccess } from "./access.js";
|
import { VolcengineAccess } from "./access.js";
|
||||||
import { HttpClient, ILogger } from "@certd/basic";
|
import { HttpClient, ILogger } from "@certd/basic";
|
||||||
import { ImportRuntime } from "@certd/pipeline";
|
|
||||||
|
|
||||||
export type VolcengineOpts = {
|
export type VolcengineOpts = {
|
||||||
access: VolcengineAccess;
|
access: VolcengineAccess;
|
||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
http: HttpClient;
|
http: HttpClient;
|
||||||
importRuntime?: ImportRuntime;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class VolcengineClient {
|
export class VolcengineClient {
|
||||||
@@ -17,6 +15,10 @@ export class VolcengineClient {
|
|||||||
this.opts = opts;
|
this.opts = opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async importRuntime(packageName: string) {
|
||||||
|
return this.opts.access.importRuntime(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
async getCertCenterService() {
|
async getCertCenterService() {
|
||||||
const CommonService = await this.getServiceCls();
|
const CommonService = await this.getServiceCls();
|
||||||
|
|
||||||
@@ -142,8 +144,7 @@ export class VolcengineClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getTOSService(opts: { region?: string }) {
|
async getTOSService(opts: { region?: string }) {
|
||||||
const importRuntime = this.opts.importRuntime || this.opts.access.importRuntime.bind(this.opts.access);
|
const { TosClient } = await this.importRuntime("@volcengine/tos-sdk");
|
||||||
const { TosClient } = await importRuntime("@volcengine/tos-sdk");
|
|
||||||
|
|
||||||
const client = new TosClient({
|
const client = new TosClient({
|
||||||
accessKeyId: this.opts.access.accessKeyId,
|
accessKeyId: this.opts.access.accessKeyId,
|
||||||
@@ -172,8 +173,7 @@ export class VolcengineClient {
|
|||||||
if (this.CommonService) {
|
if (this.CommonService) {
|
||||||
return this.CommonService;
|
return this.CommonService;
|
||||||
}
|
}
|
||||||
const importRuntime = this.opts.importRuntime || this.opts.access.importRuntime.bind(this.opts.access);
|
const { Service } = await this.importRuntime("@volcengine/openapi");
|
||||||
const { Service } = await importRuntime("@volcengine/openapi");
|
|
||||||
|
|
||||||
class CommonService extends Service {
|
class CommonService extends Service {
|
||||||
Generic: any;
|
Generic: any;
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export class VolcengineDnsProvider extends AbstractDnsProvider {
|
|||||||
access: this.access,
|
access: this.access,
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
http: this.http,
|
http: this.http,
|
||||||
importRuntime: this.importRuntime.bind(this),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
23:48
|
02:48
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
00:00
|
03:41
|
||||||
|
|||||||
Reference in New Issue
Block a user