mirror of
https://github.com/certd/certd.git
synced 2026-07-16 18:57:32 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83495b3213 | |||
| 5108416904 | |||
| 5589da1822 | |||
| 21e5aed3f3 | |||
| 4a88f795e1 | |||
| 604fa5be63 | |||
| 7ed1be994f | |||
| 6cc74a1c0a | |||
| 167b303fae | |||
| b91c9e4ea6 |
@@ -3,6 +3,18 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复上传到cos报runtimeDepsService未初始化的问题 ([167b303](https://github.com/certd/certd/commit/167b303faeca02cc11cf97e4be2a3df914852167))
|
||||||
|
* 修复dingtalk通知格式没有换行的bug ([7ed1be9](https://github.com/certd/certd/commit/7ed1be994f8b4b74cdeb38743060c912c027248b))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 给SQLITE_IOERR_WRITE增加友好报错提示,将certd:latest镜像改为certd:slim ([b91c9e4](https://github.com/certd/certd/commit/b91c9e4ea671cb359ef164e27864de1d66cba9d3))
|
||||||
|
* 优化vke keubconfig获取方式,改成先查询,如果没有再创建临时config ([604fa5b](https://github.com/certd/certd/commit/604fa5be634d099d797bfee5c2b0f26ce0ac8461))
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
+1
-1
@@ -9,5 +9,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"version": "1.42.4"
|
"version": "1.42.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
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.5](https://github.com/publishlab/node-acme-client/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|
||||||
## [1.42.4](https://github.com/publishlab/node-acme-client/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/publishlab/node-acme-client/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/acme-client
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"description": "Simple and unopinionated ACME client",
|
"description": "Simple and unopinionated ACME client",
|
||||||
"private": false,
|
"private": false,
|
||||||
"author": "nmorsman",
|
"author": "nmorsman",
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"types"
|
"types"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.42.4",
|
"@certd/basic": "^1.42.5",
|
||||||
"@peculiar/x509": "^1.11.0",
|
"@peculiar/x509": "^1.11.0",
|
||||||
"asn1js": "^3.0.5",
|
"asn1js": "^3.0.5",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/basic
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
02:45
|
23:31
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/basic",
|
"name": "@certd/basic",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
|
|||||||
@@ -3,6 +3,16 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复上传到cos报runtimeDepsService未初始化的问题 ([167b303](https://github.com/certd/certd/commit/167b303faeca02cc11cf97e4be2a3df914852167))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 优化vke keubconfig获取方式,改成先查询,如果没有再创建临时config ([604fa5b](https://github.com/certd/certd/commit/604fa5be634d099d797bfee5c2b0f26ce0ac8461))
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/pipeline",
|
"name": "@certd/pipeline",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
"lint": "eslint --fix"
|
"lint": "eslint --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.42.4",
|
"@certd/basic": "^1.42.5",
|
||||||
"@certd/plus-core": "^1.42.4",
|
"@certd/plus-core": "^1.42.5",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"reflect-metadata": "^0.2.2"
|
"reflect-metadata": "^0.2.2"
|
||||||
|
|||||||
@@ -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,16 +89,16 @@ 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) {
|
||||||
|
if (body.content) {
|
||||||
|
const content = body.content?.replace(/\n/g, " \n");
|
||||||
|
body.content = content;
|
||||||
|
}
|
||||||
return await this.send(body);
|
return await this.send(body);
|
||||||
}
|
}
|
||||||
abstract send(body: NotificationBody): Promise<void>;
|
abstract send(body: NotificationBody): Promise<void>;
|
||||||
@@ -109,9 +109,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) {
|
||||||
|
|||||||
+57
-222
@@ -1,38 +1,35 @@
|
|||||||
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, NpmRegistryResolver, type RuntimeDependencyPluginDefine } from "./runtime.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", () => {
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({}, null);
|
||||||
const merged = service.collectDependencies([
|
const merged = service.collectDependencies([
|
||||||
{ 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);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reports incompatible dependency ranges", () => {
|
it("reports incompatible dependency ranges", () => {
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({}, null);
|
||||||
const merged = service.collectDependencies([
|
const merged = service.collectDependencies([
|
||||||
{ 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 }, null);
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
service.registryResolver = {
|
service.registryResolver = {
|
||||||
async resolve() {
|
async resolve() {
|
||||||
return "https://registry.npmmirror.com";
|
return "https://registry.npmmirror.com";
|
||||||
@@ -50,18 +47,15 @@ describe("RuntimeDepsService", () => {
|
|||||||
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 }, null);
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
service.registryResolver = {
|
service.registryResolver = {
|
||||||
async resolve() {
|
async resolve() {
|
||||||
return "";
|
return "";
|
||||||
@@ -77,9 +71,7 @@ describe("RuntimeDepsService", () => {
|
|||||||
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,27 +83,19 @@ 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 }, null);
|
||||||
const service = new RuntimeDepsService();
|
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
service.commandRunner = {
|
service.commandRunner = {
|
||||||
async run() {
|
async run() {
|
||||||
throw new Error("install should not run");
|
throw new Error("install should not run");
|
||||||
},
|
},
|
||||||
} as any;
|
} 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" } }, null);
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
service.lazyDependencies = {
|
|
||||||
"lazy-pkg": "^1.2.3",
|
|
||||||
};
|
|
||||||
service.registryResolver = {
|
service.registryResolver = {
|
||||||
async resolve() {
|
async resolve() {
|
||||||
return "";
|
return "";
|
||||||
@@ -130,9 +114,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,11 +122,7 @@ 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" } }, null);
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
service.lazyDependencies = {
|
|
||||||
"@scope/lazy": "^2.0.0",
|
|
||||||
};
|
|
||||||
service.registryResolver = {
|
service.registryResolver = {
|
||||||
async resolve() {
|
async resolve() {
|
||||||
return "";
|
return "";
|
||||||
@@ -163,9 +141,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,55 +149,20 @@ 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: {} }, null);
|
||||||
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: {} }, null);
|
||||||
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 }, null);
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
service.registryResolver = {
|
service.registryResolver = {
|
||||||
async resolve() {
|
async resolve() {
|
||||||
return "";
|
return "";
|
||||||
@@ -237,22 +178,17 @@ describe("RuntimeDepsService", () => {
|
|||||||
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, pluginRegistry });
|
||||||
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,
|
||||||
target: async () => ({} as any),
|
target: async () => ({}) as any,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const resolved = service.resolvePluginDependencies({
|
const resolved = service.resolvePluginDependencies({
|
||||||
@@ -262,11 +198,7 @@ describe("RuntimeDepsService", () => {
|
|||||||
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,8 +206,7 @@ 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 }, { pluginRegistry, accessRegistry });
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
service.registryResolver = {
|
service.registryResolver = {
|
||||||
async resolve() {
|
async resolve() {
|
||||||
return "";
|
return "";
|
||||||
@@ -293,11 +224,11 @@ describe("RuntimeDepsService", () => {
|
|||||||
} as any;
|
} as any;
|
||||||
pluginRegistry.register("runtimeDepsKey", {
|
pluginRegistry.register("runtimeDepsKey", {
|
||||||
define: { name: "runtimeDepsKey", title: "key", dependPackages: { keyed: "^1.0.0" } } as any,
|
define: { name: "runtimeDepsKey", title: "key", dependPackages: { keyed: "^1.0.0" } } as any,
|
||||||
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,58 +236,16 @@ 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({}, { accessRegistry, pluginRegistry });
|
||||||
|
|
||||||
assert.throws(() => service.resolvePluginDependencies({ name: "deploy", pluginType: "deploy", dependPlugins: { "access:access": "*" } }), /插件依赖缺失/);
|
assert.throws(() => service.resolvePluginDependencies({ name: "deploy", pluginType: "deploy", dependPlugins: { "access:access": "*" } }), /插件依赖缺失/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reports incompatible dependent plugin versions", () => {
|
it("reports incompatible dependent plugin versions", () => {
|
||||||
const service = new RuntimeDepsService();
|
const service = new RuntimeDepsService({}, { accessRegistry, pluginRegistry });
|
||||||
accessRegistry.register("runtimeDepsVersionedAccess", {
|
accessRegistry.register("runtimeDepsVersionedAccess", {
|
||||||
define: { name: "runtimeDepsVersionedAccess", title: "access", version: "1.4.0", dependPackages: { accessOnly: "^1.0.0" } } as any,
|
define: { name: "runtimeDepsVersionedAccess", title: "access", version: "1.4.0", dependPackages: { accessOnly: "^1.0.0" } } as any,
|
||||||
target: async () => ({} as any),
|
target: async () => ({}) as any,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
assert.throws(
|
assert.throws(
|
||||||
@@ -374,73 +263,10 @@ 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({}, null);
|
||||||
|
|
||||||
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,8 +274,7 @@ 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 }, null);
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
service.registryResolver = {
|
service.registryResolver = {
|
||||||
async resolve() {
|
async resolve() {
|
||||||
return "";
|
return "";
|
||||||
@@ -467,7 +292,6 @@ describe("RuntimeDepsService", () => {
|
|||||||
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) {
|
||||||
@@ -483,27 +307,38 @@ describe("RuntimeDepsService", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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 }, null);
|
||||||
service.runtimeDepsRootDir = rootDir;
|
|
||||||
|
|
||||||
await assert.rejects(() => service.clearRuntimeDeps(), /动态依赖目录配置异常/);
|
await assert.rejects(() => service.clearRuntimeDeps(), /动态依赖目录配置异常/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("NpmRegistryResolver", () => {
|
||||||
|
it("chooses the fastest successful registry in auto mode", async () => {
|
||||||
|
const resolver = new NpmRegistryResolver({
|
||||||
|
mode: "auto",
|
||||||
|
candidates: ["https://slow.example.com", "https://fast.example.com"],
|
||||||
|
probeTimeoutMs: 100,
|
||||||
|
cacheTtlMs: 1000,
|
||||||
|
});
|
||||||
|
resolver.probe = async (registryUrl: string) => ({
|
||||||
|
registryUrl,
|
||||||
|
ok: true,
|
||||||
|
elapsedMs: registryUrl.includes("fast") ? 10 : 50,
|
||||||
|
});
|
||||||
|
const result = await resolver.resolve();
|
||||||
|
assert.equal(result, "https://fast.example.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses fixed registry without probing", async () => {
|
||||||
|
const resolver = new NpmRegistryResolver({
|
||||||
|
mode: "fixed",
|
||||||
|
fixedUrl: "https://registry.example.com",
|
||||||
|
probeTimeoutMs: 100,
|
||||||
|
cacheTtlMs: 1000,
|
||||||
|
});
|
||||||
|
const result = await resolver.resolve();
|
||||||
|
assert.equal(result, "https://registry.example.com");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,27 +1,773 @@
|
|||||||
/**
|
import fs from "fs";
|
||||||
* 运行时动态导入函数类型
|
import path from "path";
|
||||||
*/
|
import { spawn } from "child_process";
|
||||||
export type ImportRuntime = (specifier: string, logger?: ILogger) => Promise<any>;
|
import crypto from "crypto";
|
||||||
|
import { createRequire } from "module";
|
||||||
/**
|
import { pathToFileURL } from "url";
|
||||||
* 日志接口
|
import { logger as defaultLogger } from "@certd/basic";
|
||||||
*/
|
import type { Registry } from "../registry/registry.js";
|
||||||
export type ILogger = {
|
export type ILogger = {
|
||||||
info: (message: string) => void;
|
info: (message: string) => void;
|
||||||
|
warn?: (message: string) => void;
|
||||||
|
error?: (message: string, ...args: any[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export type ImportRuntime = (specifier: string, logger?: ILogger) => Promise<any>;
|
||||||
* 运行时依赖服务参数
|
|
||||||
*/
|
|
||||||
export type EnsureRuntimeDepsOptions = {
|
export type EnsureRuntimeDepsOptions = {
|
||||||
pluginKeys: string | string[];
|
pluginKeys: string | string[];
|
||||||
logger?: ILogger;
|
logger?: ILogger;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 运行时依赖服务接口
|
|
||||||
*/
|
|
||||||
export interface IRuntimeDepsService {
|
export interface IRuntimeDepsService {
|
||||||
ensureRuntimeDependencies(options: EnsureRuntimeDepsOptions): Promise<any>;
|
ensureRuntimeDependencies(options: EnsureRuntimeDepsOptions): Promise<any>;
|
||||||
importRuntime: ImportRuntime;
|
importRuntime: ImportRuntime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RuntimeDependencyPluginDefine = {
|
||||||
|
name: string;
|
||||||
|
key?: string;
|
||||||
|
title?: string;
|
||||||
|
version?: string;
|
||||||
|
pluginType?: string;
|
||||||
|
addonType?: string;
|
||||||
|
dependPlugins?: Record<string, string>;
|
||||||
|
dependPackages?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RegisteredDefineLike = RuntimeDependencyPluginDefine & {
|
||||||
|
key?: string;
|
||||||
|
pluginType?: string;
|
||||||
|
addonType?: string;
|
||||||
|
dependPlugins?: Record<string, string>;
|
||||||
|
dependPackages?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DependencyConflict = {
|
||||||
|
packageName: string;
|
||||||
|
ranges: Array<{ pluginName: string; range: string }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CollectDependenciesResult = {
|
||||||
|
dependencies: Record<string, string>;
|
||||||
|
conflicts: DependencyConflict[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type InstallResult = {
|
||||||
|
registryUrl: string;
|
||||||
|
packageJsonPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RuntimeImportResolveResult = {
|
||||||
|
resolved: string;
|
||||||
|
packageName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CommandRunnerResult = {
|
||||||
|
stdout: string;
|
||||||
|
stderr: string;
|
||||||
|
code: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CommandRunner = {
|
||||||
|
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>>();
|
||||||
|
|
||||||
|
class DefaultCommandRunner implements CommandRunner {
|
||||||
|
async run(command: string, args: string[], options: { cwd: string; timeoutMs: number; env?: NodeJS.ProcessEnv }): Promise<CommandRunnerResult> {
|
||||||
|
return await new Promise<CommandRunnerResult>(resolve => {
|
||||||
|
let stdout = "";
|
||||||
|
let stderr = "";
|
||||||
|
let settled = false;
|
||||||
|
const child = spawn(command, args, { cwd: options.cwd, env: options.env, windowsHide: true, shell: process.platform === "win32" });
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
if (settled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settled = true;
|
||||||
|
child.kill("SIGTERM");
|
||||||
|
resolve({ stdout, stderr: stderr || `command timeout after ${options.timeoutMs}ms`, code: 1 });
|
||||||
|
}, options.timeoutMs);
|
||||||
|
child.stdout?.on("data", chunk => {
|
||||||
|
stdout += chunk.toString();
|
||||||
|
});
|
||||||
|
child.stderr?.on("data", chunk => {
|
||||||
|
stderr += chunk.toString();
|
||||||
|
});
|
||||||
|
child.on("error", error => {
|
||||||
|
if (settled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settled = true;
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve({ stdout, stderr: error.message, code: 1 });
|
||||||
|
});
|
||||||
|
child.on("close", code => {
|
||||||
|
if (settled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settled = true;
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve({ stdout, stderr, code: code || 0 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RuntimeDepsService {
|
||||||
|
runtimeDepsRootDir: string;
|
||||||
|
autoInstall: boolean;
|
||||||
|
enabled: boolean;
|
||||||
|
installTimeoutMs: number;
|
||||||
|
pnpmCommand: string;
|
||||||
|
lazyDependencies: Record<string, string>;
|
||||||
|
registryResolver!: NpmRegistryResolver;
|
||||||
|
commandRunner: CommandRunner = new DefaultCommandRunner();
|
||||||
|
pluginLazyDependencies: Record<string, string> = {};
|
||||||
|
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 {
|
||||||
|
const merged: Record<string, string> = {};
|
||||||
|
const seen: Record<string, Array<{ pluginName: string; range: string }>> = {};
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
const deps = plugin.dependPackages || {};
|
||||||
|
for (const [packageName, range] of Object.entries(deps)) {
|
||||||
|
seen[packageName] ||= [];
|
||||||
|
seen[packageName].push({ pluginName: plugin.name, range });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const conflicts: DependencyConflict[] = [];
|
||||||
|
for (const [packageName, ranges] of Object.entries(seen)) {
|
||||||
|
const first = ranges[0]?.range;
|
||||||
|
if (!first) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const conflict = ranges.some(item => !areRangesCompatible(first, item.range));
|
||||||
|
if (conflict) {
|
||||||
|
conflicts.push({ packageName, ranges });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
merged[packageName] = first;
|
||||||
|
}
|
||||||
|
return { dependencies: merged, conflicts };
|
||||||
|
}
|
||||||
|
|
||||||
|
async ensureInstalled(options: { plugins: RuntimeDependencyPluginDefine[]; logger?: ILogger }): Promise<InstallResult> {
|
||||||
|
const { plugins, logger: log } = options;
|
||||||
|
const { dependencies, conflicts } = this.resolveDependenciesFromPlugins(plugins);
|
||||||
|
if (conflicts.length > 0) {
|
||||||
|
const conflict = conflicts[0];
|
||||||
|
throw new Error(`动态依赖版本冲突: ${conflict.packageName} => ${conflict.ranges.map(item => `${item.pluginName}:${item.range}`).join(", ")}`);
|
||||||
|
}
|
||||||
|
return await this.ensureDependencies({ dependencies, logger: log });
|
||||||
|
}
|
||||||
|
|
||||||
|
async ensureDependencies(options: { dependencies: Record<string, string>; logger?: ILogger }): Promise<InstallResult> {
|
||||||
|
const { dependencies, logger: log } = options;
|
||||||
|
if (!this.enabled) {
|
||||||
|
return { registryUrl: "", packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json") };
|
||||||
|
}
|
||||||
|
if (!this.autoInstall) {
|
||||||
|
return { registryUrl: "", packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json") };
|
||||||
|
}
|
||||||
|
const dependenciesHash = this.createDependenciesHash(dependencies);
|
||||||
|
let installPromise = this.installPromises.get(dependenciesHash);
|
||||||
|
if (installPromise) {
|
||||||
|
const nodeModulesPath = path.join(this.getRuntimeDepsRootDir(), "node_modules");
|
||||||
|
if (!fs.existsSync(nodeModulesPath)) {
|
||||||
|
this.installPromises.delete(dependenciesHash);
|
||||||
|
installPromise = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!installPromise) {
|
||||||
|
installPromise = this.doEnsureInstalled({ dependencies, logger: log }).catch(error => {
|
||||||
|
this.installPromises.delete(dependenciesHash);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
this.installPromises.set(dependenciesHash, installPromise);
|
||||||
|
}
|
||||||
|
return await installPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveDependenciesFromPlugins(plugins: RuntimeDependencyPluginDefine[]): CollectDependenciesResult {
|
||||||
|
const expandedPlugins = plugins.flatMap(plugin => this.resolvePluginDependencies(plugin));
|
||||||
|
return this.collectDependencies(expandedPlugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ensureRuntimeDependencies(options: { pluginKeys: string | string[]; logger?: ILogger }): Promise<InstallResult> {
|
||||||
|
const { pluginKeys, logger: log } = options;
|
||||||
|
const keys = Array.isArray(pluginKeys) ? pluginKeys : [pluginKeys];
|
||||||
|
const pluginDefines = keys.map(pluginKey => this.getDefineByPluginKey(pluginKey));
|
||||||
|
if (pluginDefines.every(pluginDefine => !pluginDefine.dependPackages && !pluginDefine.dependPlugins)) {
|
||||||
|
return { registryUrl: "", packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json") };
|
||||||
|
}
|
||||||
|
const expandedPluginDefines = pluginDefines.flatMap(pluginDefine => this.resolvePluginDependencies(pluginDefine));
|
||||||
|
return await this.ensureInstalled({ plugins: expandedPluginDefines, logger: log });
|
||||||
|
}
|
||||||
|
|
||||||
|
async importRuntime(specifier: string, logger: ILogger = defaultLogger) {
|
||||||
|
if (this.isNativeImportSpecifier(specifier)) {
|
||||||
|
return await import(specifier);
|
||||||
|
}
|
||||||
|
const resolved = await this.resolveImportSpecifier(specifier, logger);
|
||||||
|
return await import(pathToFileURL(resolved).href);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveImportSpecifier(specifier: string, logger: ILogger = defaultLogger) {
|
||||||
|
try {
|
||||||
|
return this.resolveRuntimeSpecifier(specifier).resolved;
|
||||||
|
} catch (runtimeError: any) {
|
||||||
|
if (!this.isModuleNotFoundError(runtimeError)) {
|
||||||
|
throw runtimeError;
|
||||||
|
}
|
||||||
|
return await this.resolveMissingRuntimeSpecifier(specifier, runtimeError, logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveMissingRuntimeSpecifier(specifier: string, runtimeError: any, logger?: ILogger) {
|
||||||
|
const packageName = this.parsePackageName(specifier);
|
||||||
|
const mergedDeps = this.getMergedLazyDependencies();
|
||||||
|
const lazyRange = mergedDeps[packageName];
|
||||||
|
if (!lazyRange) {
|
||||||
|
try {
|
||||||
|
return this.resolveProjectSpecifier(specifier, runtimeError).resolved;
|
||||||
|
} catch {
|
||||||
|
throw new Error(`动态依赖未安装且未配置懒加载版本: ${packageName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.ensureLazyDependency(packageName, logger);
|
||||||
|
return this.resolveRuntimeSpecifier(specifier).resolved;
|
||||||
|
} catch (lazyError: any) {
|
||||||
|
return this.resolveProjectSpecifier(specifier, lazyError).resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isNativeImportSpecifier(specifier: string) {
|
||||||
|
return specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:");
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveRuntimeSpecifier(specifier: string): RuntimeImportResolveResult {
|
||||||
|
const packageName = this.parsePackageName(specifier);
|
||||||
|
const packageJsonPath = path.join(this.getRuntimeDepsRootDir(), "package.json");
|
||||||
|
const require = createRequire(packageJsonPath);
|
||||||
|
const resolved = require.resolve(specifier);
|
||||||
|
return { packageName, resolved };
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveProjectSpecifier(specifier: string, cause?: any): RuntimeImportResolveResult {
|
||||||
|
try {
|
||||||
|
const packageName = this.parsePackageName(specifier);
|
||||||
|
const packageJsonPath = path.resolve("package.json");
|
||||||
|
const require = createRequire(packageJsonPath);
|
||||||
|
const resolved = require.resolve(specifier);
|
||||||
|
return { packageName, resolved };
|
||||||
|
} catch (projectError: any) {
|
||||||
|
if (cause) {
|
||||||
|
projectError.cause = cause;
|
||||||
|
}
|
||||||
|
throw projectError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parsePackageName(specifier: string) {
|
||||||
|
if (!specifier || specifier.trim() !== specifier) {
|
||||||
|
throw new Error(`动态依赖导入路径无效: ${specifier}`);
|
||||||
|
}
|
||||||
|
const parts = specifier.split("/");
|
||||||
|
if (specifier.startsWith("@")) {
|
||||||
|
if (parts.length < 2 || !parts[0] || !parts[1]) {
|
||||||
|
throw new Error(`动态依赖导入路径无效: ${specifier}`);
|
||||||
|
}
|
||||||
|
return `${parts[0]}/${parts[1]}`;
|
||||||
|
}
|
||||||
|
if (!parts[0]) {
|
||||||
|
throw new Error(`动态依赖导入路径无效: ${specifier}`);
|
||||||
|
}
|
||||||
|
return parts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureLazyDependency(packageName: string, logger?: ILogger) {
|
||||||
|
const range = this.lazyDependencies?.[packageName];
|
||||||
|
if (!range) {
|
||||||
|
throw new Error(`动态依赖未安装且未配置懒加载版本: ${packageName}`);
|
||||||
|
}
|
||||||
|
await this.ensureDependencies({ dependencies: { [packageName]: range }, logger });
|
||||||
|
}
|
||||||
|
|
||||||
|
private isModuleNotFoundError(error: any) {
|
||||||
|
return error?.code === "MODULE_NOT_FOUND" || error?.code === "ERR_MODULE_NOT_FOUND";
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvePluginDependencies(current: RuntimeDependencyPluginDefine): RuntimeDependencyPluginDefine[] {
|
||||||
|
const resolved: RuntimeDependencyPluginDefine[] = [];
|
||||||
|
const visited = new Set<string>();
|
||||||
|
const visit = (item: RuntimeDependencyPluginDefine) => {
|
||||||
|
const key = this.buildPluginDependencyKey(item);
|
||||||
|
if (visited.has(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visited.add(key);
|
||||||
|
resolved.push(item);
|
||||||
|
for (const [dependencyName, expectedRange] of Object.entries(item.dependPlugins || {})) {
|
||||||
|
const dependency = this.getDefineByPluginKey(dependencyName, item);
|
||||||
|
if (!isPluginVersionCompatible(dependency, expectedRange)) {
|
||||||
|
throw new Error(`插件依赖版本冲突: ${item.name} 依赖 ${dependencyName}@${expectedRange},当前版本为 ${dependency.version || "未声明"}`);
|
||||||
|
}
|
||||||
|
visit(dependency);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
visit(current);
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildPluginDependencyKey(plugin: RuntimeDependencyPluginDefine) {
|
||||||
|
if (plugin.pluginType === "addon" && plugin.addonType) {
|
||||||
|
return `addon:${plugin.addonType}:${plugin.name}`;
|
||||||
|
}
|
||||||
|
const pluginType = plugin.pluginType === "deploy" ? "plugin" : plugin.pluginType || "unknown";
|
||||||
|
return `${pluginType}:${plugin.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDefineByPluginKey(pluginKey: string, owner?: RuntimeDependencyPluginDefine): RuntimeDependencyPluginDefine {
|
||||||
|
const parts = pluginKey.split(":");
|
||||||
|
let pluginType: string, name: string, subtype: string | undefined;
|
||||||
|
if (parts.length === 2) {
|
||||||
|
[pluginType, name] = parts;
|
||||||
|
} else if (parts.length === 3) {
|
||||||
|
[pluginType, subtype, name] = parts;
|
||||||
|
} else {
|
||||||
|
const ownerName = owner?.name || pluginKey;
|
||||||
|
throw new Error(`插件依赖格式错误: ${ownerName} 依赖 ${pluginKey}`);
|
||||||
|
}
|
||||||
|
if (!this.registriesMap) {
|
||||||
|
throw new Error("注册表未设置,请先调用 setRegistries");
|
||||||
|
}
|
||||||
|
const target = this.registriesMap[pluginType];
|
||||||
|
if (!target) {
|
||||||
|
const ownerName = owner?.name || pluginKey;
|
||||||
|
throw new Error(`插件依赖格式错误: ${ownerName} 依赖 ${pluginKey},未知插件类型 ${pluginType}`);
|
||||||
|
}
|
||||||
|
// addon 类型的 key 需要包含 subtype
|
||||||
|
const registryKey = pluginType === "addon" && subtype ? `${subtype}:${name}` : name;
|
||||||
|
const define = target.registry.getDefine(registryKey) as RegisteredDefineLike;
|
||||||
|
if (!define) {
|
||||||
|
throw new Error(`插件依赖缺失: ${owner?.name || pluginKey} 依赖 ${pluginKey},但该插件未注册或已禁用`);
|
||||||
|
}
|
||||||
|
return { ...define, key: pluginKey, pluginType: target.pluginType, addonType: target.addonType };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doEnsureInstalled(options: { dependencies: Record<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> {
|
||||||
|
const rootDir = this.getRuntimeDepsRootDir();
|
||||||
|
fs.mkdirSync(rootDir, { recursive: true });
|
||||||
|
const lockFile = path.join(rootDir, ".install.lock");
|
||||||
|
const previous = PROCESS_LOCKS.get(lockFile);
|
||||||
|
if (previous) {
|
||||||
|
await previous.catch(() => undefined);
|
||||||
|
}
|
||||||
|
let releaseProcessLock!: () => void;
|
||||||
|
const current = new Promise<void>(resolve => {
|
||||||
|
releaseProcessLock = resolve;
|
||||||
|
});
|
||||||
|
PROCESS_LOCKS.set(lockFile, current);
|
||||||
|
let fd: number | undefined;
|
||||||
|
try {
|
||||||
|
fd = await this.acquireFileLock(lockFile);
|
||||||
|
return await run();
|
||||||
|
} finally {
|
||||||
|
if (fd != null) {
|
||||||
|
fs.closeSync(fd);
|
||||||
|
try {
|
||||||
|
fs.rmSync(lockFile, { force: true });
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
fs.rmSync(lockFile, { force: true });
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
releaseProcessLock();
|
||||||
|
if (PROCESS_LOCKS.get(lockFile) === current) {
|
||||||
|
PROCESS_LOCKS.delete(lockFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async acquireFileLock(lockFile: string) {
|
||||||
|
const deadline = Date.now() + this.installTimeoutMs;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
const fd = fs.openSync(lockFile, "wx");
|
||||||
|
fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }), "utf8");
|
||||||
|
return fd;
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error?.code !== "EEXIST") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if (Date.now() > deadline) {
|
||||||
|
throw new Error(`动态依赖安装锁等待超时: ${lockFile}`);
|
||||||
|
}
|
||||||
|
await this.waitForExternalLock(lockFile, deadline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async waitForExternalLock(lockFile: string, deadline: number) {
|
||||||
|
while (fs.existsSync(lockFile)) {
|
||||||
|
if (Date.now() > deadline) {
|
||||||
|
throw new Error(`动态依赖安装锁等待超时: ${lockFile}`);
|
||||||
|
}
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 300));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearRuntimeDeps() {
|
||||||
|
const rootDir = this.getRuntimeDepsRootDir();
|
||||||
|
const normalizedRootDir = path.normalize(rootDir);
|
||||||
|
if (!normalizedRootDir.endsWith(path.normalize(".runtime-deps"))) {
|
||||||
|
throw new Error(`动态依赖目录配置异常,拒绝清理: ${rootDir}`);
|
||||||
|
}
|
||||||
|
await this.withInstallLock(async () => {
|
||||||
|
if (fs.existsSync(rootDir)) {
|
||||||
|
const entries = fs.readdirSync(rootDir);
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry === ".install.lock") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fs.rmSync(path.join(rootDir, entry), { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.installPromises.clear();
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getMergedLazyDependencies(): Record<string, string> {
|
||||||
|
return { ...this.lazyDependencies, ...this.pluginLazyDependencies };
|
||||||
|
}
|
||||||
|
|
||||||
|
collectPluginDeps(logger?: ILogger) {
|
||||||
|
if (!this.registriesMap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const deps: Record<string, string> = {};
|
||||||
|
for (const { registry } of Object.values(this.registriesMap)) {
|
||||||
|
const defineList = registry.getDefineList();
|
||||||
|
for (const define of defineList) {
|
||||||
|
const dependPackages = (define as any).dependPackages as Record<string, string> | undefined;
|
||||||
|
if (!dependPackages) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const [pkgName, range] of Object.entries(dependPackages)) {
|
||||||
|
const existing = deps[pkgName];
|
||||||
|
if (existing && !areRangesCompatible(existing, range)) {
|
||||||
|
(logger || defaultLogger).warn?.(`懒加载依赖版本冲突: ${pkgName} => ${existing} vs ${range},保留已有版本`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
deps[pkgName] = range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.pluginLazyDependencies = deps;
|
||||||
|
(logger || defaultLogger).info(`从插件注册表收集到 ${Object.keys(deps).length} 个懒加载依赖`);
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshPluginDeps(logger?: ILogger) {
|
||||||
|
this.collectPluginDeps(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readInstallState(statePath: string): any {
|
||||||
|
if (!fs.existsSync(statePath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(fs.readFileSync(statePath, "utf8"));
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private writeInstallState(statePath: string, state: any) {
|
||||||
|
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
private readManifestDependencies(packageJsonPath: string): Record<string, string> {
|
||||||
|
if (!fs.existsSync(packageJsonPath)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const manifest = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
||||||
|
return manifest.dependencies || {};
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private mergeInstalledDependencies(installed: Record<string, string>, requested: Record<string, string>) {
|
||||||
|
const dependencies = { ...installed };
|
||||||
|
for (const [packageName, range] of Object.entries(requested)) {
|
||||||
|
const installedRange = dependencies[packageName];
|
||||||
|
if (installedRange && !areRangesCompatible(installedRange, range)) {
|
||||||
|
throw new Error(`动态依赖版本冲突: ${packageName} => installed:${installedRange}, requested:${range}`);
|
||||||
|
}
|
||||||
|
dependencies[packageName] = installedRange || range;
|
||||||
|
}
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getPnpmVersion(command: string, env: NodeJS.ProcessEnv) {
|
||||||
|
const result = await this.commandRunner.run(command, ["--version"], { cwd: this.getRuntimeDepsRootDir(), timeoutMs: Math.min(this.installTimeoutMs, 10000), env });
|
||||||
|
if (result.code !== 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return (result.stdout || result.stderr || "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPnpmCommand() {
|
||||||
|
return this.pnpmCommand || "pnpm";
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildChildEnv(registryUrl: string) {
|
||||||
|
const env = { ...process.env };
|
||||||
|
for (const key of ["NODE_OPTIONS", "VSCODE_INSPECTOR_OPTIONS", "NODE_INSPECTOR_PORT", "NODE_DEBUG"]) {
|
||||||
|
if (!env[key]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (key === "NODE_OPTIONS") {
|
||||||
|
env[key] = this.stripDebugNodeOptions(env[key] as string);
|
||||||
|
} else {
|
||||||
|
delete env[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (registryUrl) {
|
||||||
|
env.npm_config_registry = registryUrl;
|
||||||
|
env.pnpm_config_registry = registryUrl;
|
||||||
|
}
|
||||||
|
env.CI = env.CI || "true";
|
||||||
|
env.npm_config_confirm_modules_purge = "false";
|
||||||
|
env.pnpm_config_confirm_modules_purge = "false";
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
private stripDebugNodeOptions(value: string) {
|
||||||
|
return value
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.filter(item => !/^--inspect(-brk|-port)?(=|$)/.test(item))
|
||||||
|
.filter(item => !/^--debug(=|$)/.test(item))
|
||||||
|
.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
getRuntimeDepsRootDir() {
|
||||||
|
return path.resolve(this.runtimeDepsRootDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createDependenciesHash(dependencies: Record<string, string>) {
|
||||||
|
return crypto.createHash("sha256").update(JSON.stringify(dependencies)).digest("hex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPluginVersionCompatible(plugin: RuntimeDependencyPluginDefine, expectedRange: string) {
|
||||||
|
if (!expectedRange || expectedRange === "*") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!plugin.version) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-huawei
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-huawei",
|
"name": "@certd/lib-huawei",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
"types": "./dist/d/index.d.ts",
|
"types": "./dist/d/index.d.ts",
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||||
"@typescript-eslint/parser": "^8.26.1",
|
"@typescript-eslint/parser": "^8.26.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
"esmock": "^2.7.5",
|
"esmock": "^2.7.5",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
"tslib": "^2.8.1"
|
"tslib": "^2.8.1"
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-iframe
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-iframe",
|
"name": "@certd/lib-iframe",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/jdcloud
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/jdcloud",
|
"name": "@certd/jdcloud",
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"description": "jdcloud openApi sdk",
|
"description": "jdcloud openApi sdk",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
"chai": "^5.1.0",
|
"chai": "^5.1.0",
|
||||||
"config": "^1.30.0",
|
"config": "^1.30.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
"esmock": "^2.7.5",
|
"esmock": "^2.7.5",
|
||||||
"js-yaml": "^3.11.0",
|
"js-yaml": "^3.11.0",
|
||||||
"mocha": "^10.6.0",
|
"mocha": "^10.6.0",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-k8s
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-k8s",
|
"name": "@certd/lib-k8s",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"lint": "eslint --fix"
|
"lint": "eslint --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.42.4",
|
"@certd/basic": "^1.42.5",
|
||||||
"@kubernetes/client-node": "0.21.0"
|
"@kubernetes/client-node": "0.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复上传到cos报runtimeDepsService未初始化的问题 ([167b303](https://github.com/certd/certd/commit/167b303faeca02cc11cf97e4be2a3df914852167))
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-server",
|
"name": "@certd/lib-server",
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -29,11 +29,11 @@
|
|||||||
],
|
],
|
||||||
"license": "AGPL",
|
"license": "AGPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.42.4",
|
"@certd/acme-client": "^1.42.5",
|
||||||
"@certd/basic": "^1.42.4",
|
"@certd/basic": "^1.42.5",
|
||||||
"@certd/pipeline": "^1.42.4",
|
"@certd/pipeline": "^1.42.5",
|
||||||
"@certd/plugin-lib": "^1.42.4",
|
"@certd/plugin-lib": "^1.42.5",
|
||||||
"@certd/plus-core": "^1.42.4",
|
"@certd/plus-core": "^1.42.5",
|
||||||
"@midwayjs/cache": "3.14.0",
|
"@midwayjs/cache": "3.14.0",
|
||||||
"@midwayjs/core": "3.20.11",
|
"@midwayjs/core": "3.20.11",
|
||||||
"@midwayjs/i18n": "3.20.13",
|
"@midwayjs/i18n": "3.20.13",
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 给SQLITE_IOERR_WRITE增加友好报错提示,将certd:latest镜像改为certd:slim ([b91c9e4](https://github.com/certd/certd/commit/b91c9e4ea671cb359ef164e27864de1d66cba9d3))
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/midway-flyway-js",
|
"name": "@certd/midway-flyway-js",
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -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 {
|
||||||
//说明需要进出深度了
|
//说明需要进出深度了
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/plugin-cert
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-cert",
|
"name": "@certd/plugin-cert",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"lint": "eslint --fix"
|
"lint": "eslint --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/plugin-lib": "^1.42.4"
|
"@certd/plugin-lib": "^1.42.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.3.12",
|
"@types/chai": "^4.3.12",
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复上传到cos报runtimeDepsService未初始化的问题 ([167b303](https://github.com/certd/certd/commit/167b303faeca02cc11cf97e4be2a3df914852167))
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-lib",
|
"name": "@certd/plugin-lib",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
"lint": "eslint --fix"
|
"lint": "eslint --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.42.4",
|
"@certd/acme-client": "^1.42.5",
|
||||||
"@certd/basic": "^1.42.4",
|
"@certd/basic": "^1.42.5",
|
||||||
"@certd/pipeline": "^1.42.4",
|
"@certd/pipeline": "^1.42.5",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复上传到cos报runtimeDepsService未初始化的问题 ([167b303](https://github.com/certd/certd/commit/167b303faeca02cc11cf97e4be2a3df914852167))
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/ui-client
|
**Note:** Version bump only for package @certd/ui-client
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-client",
|
"name": "@certd/ui-client",
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --open",
|
"dev": "vite --open",
|
||||||
@@ -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",
|
||||||
@@ -105,8 +104,8 @@
|
|||||||
"zod-defaults": "^0.1.3"
|
"zod-defaults": "^0.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@certd/lib-iframe": "^1.42.4",
|
"@certd/lib-iframe": "^1.42.5",
|
||||||
"@certd/pipeline": "^1.42.4",
|
"@certd/pipeline": "^1.42.5",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@types/chai": "^4.3.12",
|
"@types/chai": "^4.3.12",
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"plugins": [
|
|
||||||
"@typescript-eslint"
|
|
||||||
],
|
|
||||||
"ignorePatterns": ["dist"],
|
|
||||||
"extends": [
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"mocha": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
|
||||||
"@typescript-eslint/ban-ts-ignore": "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-this-alias": "off",
|
|
||||||
// 允许any
|
|
||||||
"@typescript-eslint/no-unsafe-anyassignment": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,17 @@
|
|||||||
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.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复上传到cos报runtimeDepsService未初始化的问题 ([167b303](https://github.com/certd/certd/commit/167b303faeca02cc11cf97e4be2a3df914852167))
|
||||||
|
* 修复dingtalk通知格式没有换行的bug ([7ed1be9](https://github.com/certd/certd/commit/7ed1be994f8b4b74cdeb38743060c912c027248b))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 优化vke keubconfig获取方式,改成先查询,如果没有再创建临时config ([604fa5b](https://github.com/certd/certd/commit/604fa5be634d099d797bfee5c2b0f26ce0ac8461))
|
||||||
|
|
||||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-server",
|
"name": "@certd/ui-server",
|
||||||
"version": "1.42.4",
|
"version": "1.42.5",
|
||||||
"description": "fast-server base midway",
|
"description": "fast-server base midway",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -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",
|
||||||
@@ -41,20 +42,20 @@
|
|||||||
"lint1": "eslint --fix"
|
"lint1": "eslint --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.42.4",
|
"@certd/acme-client": "^1.42.5",
|
||||||
"@certd/basic": "^1.42.4",
|
"@certd/basic": "^1.42.5",
|
||||||
"@certd/commercial-core": "^1.42.4",
|
"@certd/commercial-core": "^1.42.5",
|
||||||
"@certd/cv4pve-api-javascript": "^8.4.2",
|
"@certd/cv4pve-api-javascript": "^8.4.2",
|
||||||
"@certd/jdcloud": "^1.42.4",
|
"@certd/jdcloud": "^1.42.5",
|
||||||
"@certd/lib-huawei": "^1.42.4",
|
"@certd/lib-huawei": "^1.42.5",
|
||||||
"@certd/lib-k8s": "^1.42.4",
|
"@certd/lib-k8s": "^1.42.5",
|
||||||
"@certd/lib-server": "^1.42.4",
|
"@certd/lib-server": "^1.42.5",
|
||||||
"@certd/midway-flyway-js": "^1.42.4",
|
"@certd/midway-flyway-js": "^1.42.5",
|
||||||
"@certd/pipeline": "^1.42.4",
|
"@certd/pipeline": "^1.42.5",
|
||||||
"@certd/plugin-cert": "^1.42.4",
|
"@certd/plugin-cert": "^1.42.5",
|
||||||
"@certd/plugin-lib": "^1.42.4",
|
"@certd/plugin-lib": "^1.42.5",
|
||||||
"@certd/plugin-plus": "^1.42.4",
|
"@certd/plugin-plus": "^1.42.5",
|
||||||
"@certd/plus-core": "^1.42.4",
|
"@certd/plus-core": "^1.42.5",
|
||||||
"@koa/cors": "^5.0.0",
|
"@koa/cors": "^5.0.0",
|
||||||
"@midwayjs/bootstrap": "3.20.11",
|
"@midwayjs/bootstrap": "3.20.11",
|
||||||
"@midwayjs/cache": "3.14.0",
|
"@midwayjs/cache": "3.14.0",
|
||||||
@@ -125,6 +126,7 @@
|
|||||||
"@types/nodemailer": "^6.4.8",
|
"@types/nodemailer": "^6.4.8",
|
||||||
"c8": "^10.1.2",
|
"c8": "^10.1.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
"eslint": "^7.32.0",
|
||||||
"esmock": "^2.7.5",
|
"esmock": "^2.7.5",
|
||||||
"mocha": "^10.6.0",
|
"mocha": "^10.6.0",
|
||||||
"mwts": "^1.3.0",
|
"mwts": "^1.3.0",
|
||||||
|
|||||||
@@ -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,8 +1,8 @@
|
|||||||
|
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";
|
||||||
import { UserService } from "../../sys/authority/service/user-service.js";
|
import { UserService } from "../../sys/authority/service/user-service.js";
|
||||||
import { RuntimeDepsService } from "../../runtime-deps/runtime-deps-service.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 授权
|
* 授权
|
||||||
@@ -14,15 +14,12 @@ export class TwoFactorService {
|
|||||||
userSettingsService: UserSettingsService;
|
userSettingsService: UserSettingsService;
|
||||||
@Inject()
|
@Inject()
|
||||||
userService: UserService;
|
userService: UserService;
|
||||||
@Inject()
|
|
||||||
runtimeDepsService: RuntimeDepsService;
|
|
||||||
|
|
||||||
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 this.runtimeDepsService.importRuntime("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);
|
||||||
@@ -41,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 this.runtimeDepsService.importRuntime("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;
|
||||||
@@ -80,7 +77,7 @@ export class TwoFactorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async verifyAuthenticatorCode(userId: any, verifyCode: string) {
|
async verifyAuthenticatorCode(userId: any, verifyCode: string) {
|
||||||
const { authenticator } = await this.runtimeDepsService.importRuntime("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,41 +0,0 @@
|
|||||||
import assert from "assert";
|
|
||||||
import { NpmRegistryResolver } from "./npm-registry-resolver.js";
|
|
||||||
|
|
||||||
describe("NpmRegistryResolver", () => {
|
|
||||||
it("chooses the fastest successful registry in auto mode", async () => {
|
|
||||||
const resolver = new NpmRegistryResolver();
|
|
||||||
resolver.config = {
|
|
||||||
mode: "auto",
|
|
||||||
fixedUrl: "",
|
|
||||||
candidates: ["https://slow.example.com", "https://fast.example.com"],
|
|
||||||
probeTimeoutMs: 100,
|
|
||||||
cacheTtlMs: 1000,
|
|
||||||
};
|
|
||||||
resolver.probe = async registryUrl => {
|
|
||||||
return {
|
|
||||||
registryUrl,
|
|
||||||
ok: true,
|
|
||||||
elapsedMs: registryUrl.includes("fast") ? 10 : 50,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await resolver.resolve();
|
|
||||||
|
|
||||||
assert.equal(result, "https://fast.example.com");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses fixed registry without probing", async () => {
|
|
||||||
const resolver = new NpmRegistryResolver();
|
|
||||||
resolver.config = {
|
|
||||||
mode: "fixed",
|
|
||||||
fixedUrl: "https://registry.example.com",
|
|
||||||
candidates: [],
|
|
||||||
probeTimeoutMs: 100,
|
|
||||||
cacheTtlMs: 1000,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await resolver.resolve();
|
|
||||||
|
|
||||||
assert.equal(result, "https://registry.example.com");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,733 +0,0 @@
|
|||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import { spawn } from "child_process";
|
|
||||||
import crypto from "crypto";
|
|
||||||
import { Config, Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
|
||||||
import { createRequire } from "module";
|
|
||||||
import { pathToFileURL } from "url";
|
|
||||||
import { NpmRegistryResolver } from "./npm-registry-resolver.js";
|
|
||||||
import { Registry, accessRegistry, notificationRegistry, pluginRegistry } from "@certd/pipeline";
|
|
||||||
import { dnsProviderRegistry } from "@certd/plugin-lib";
|
|
||||||
import { addonRegistry } from "@certd/lib-server";
|
|
||||||
import { logger, ILogger } from "@certd/basic";
|
|
||||||
|
|
||||||
export type RuntimeDependencyPluginDefine = {
|
|
||||||
name: string;
|
|
||||||
key?: string;
|
|
||||||
title?: string;
|
|
||||||
version?: string;
|
|
||||||
pluginType?: string;
|
|
||||||
addonType?: string;
|
|
||||||
dependPlugins?: Record<string, string>;
|
|
||||||
dependPackages?: Record<string, string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RegisteredDefineLike = RuntimeDependencyPluginDefine & {
|
|
||||||
key?: string;
|
|
||||||
pluginType?: string;
|
|
||||||
addonType?: string;
|
|
||||||
dependPlugins?: 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 = {
|
|
||||||
packageName: string;
|
|
||||||
ranges: Array<{ pluginName: string; range: string }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CollectDependenciesResult = {
|
|
||||||
dependencies: Record<string, string>;
|
|
||||||
conflicts: DependencyConflict[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type InstallResult = {
|
|
||||||
registryUrl: string;
|
|
||||||
packageJsonPath: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RuntimeImportResolveResult = {
|
|
||||||
resolved: string;
|
|
||||||
packageName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CommandRunnerResult = {
|
|
||||||
stdout: string;
|
|
||||||
stderr: string;
|
|
||||||
code: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CommandRunner = {
|
|
||||||
// @ts-ignore
|
|
||||||
run(command: string, args: string[], options: { cwd: string; timeoutMs: number; env?: NodeJS.ProcessEnv }): Promise<CommandRunnerResult>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PROCESS_LOCKS = new Map<string, Promise<unknown>>();
|
|
||||||
|
|
||||||
class DefaultCommandRunner implements CommandRunner {
|
|
||||||
// @ts-ignore
|
|
||||||
async run(command: string, args: string[], options: { cwd: string; timeoutMs: number; env?: NodeJS.ProcessEnv }): Promise<CommandRunnerResult> {
|
|
||||||
return await new Promise<CommandRunnerResult>(resolve => {
|
|
||||||
let stdout = "";
|
|
||||||
let stderr = "";
|
|
||||||
let settled = false;
|
|
||||||
const child = spawn(command, args, {
|
|
||||||
cwd: options.cwd,
|
|
||||||
env: options.env,
|
|
||||||
windowsHide: true,
|
|
||||||
// @ts-ignore
|
|
||||||
shell: process.platform === "win32",
|
|
||||||
});
|
|
||||||
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
if (settled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
settled = true;
|
|
||||||
child.kill("SIGTERM");
|
|
||||||
resolve({ stdout, stderr: stderr || `command timeout after ${options.timeoutMs}ms`, code: 1 });
|
|
||||||
}, options.timeoutMs);
|
|
||||||
|
|
||||||
child.stdout?.on("data", chunk => {
|
|
||||||
stdout += chunk.toString();
|
|
||||||
});
|
|
||||||
child.stderr?.on("data", chunk => {
|
|
||||||
stderr += chunk.toString();
|
|
||||||
});
|
|
||||||
child.on("error", error => {
|
|
||||||
if (settled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
settled = true;
|
|
||||||
clearTimeout(timer);
|
|
||||||
resolve({ stdout, stderr: error.message, code: 1 });
|
|
||||||
});
|
|
||||||
child.on("close", code => {
|
|
||||||
if (settled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
settled = true;
|
|
||||||
clearTimeout(timer);
|
|
||||||
resolve({ stdout, stderr, code: code || 0 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provide()
|
|
||||||
@Scope(ScopeEnum.Singleton)
|
|
||||||
export class RuntimeDepsService {
|
|
||||||
@Config("runtimeDeps.rootDir")
|
|
||||||
runtimeDepsRootDir = "./data/.runtime-deps";
|
|
||||||
|
|
||||||
@Config("runtimeDeps.autoInstall")
|
|
||||||
autoInstall = true;
|
|
||||||
|
|
||||||
@Config("runtimeDeps.enabled")
|
|
||||||
enabled = true;
|
|
||||||
|
|
||||||
@Config("runtimeDeps.installTimeoutMs")
|
|
||||||
installTimeoutMs = 120000;
|
|
||||||
|
|
||||||
@Config("runtimeDeps.pnpmCommand")
|
|
||||||
pnpmCommand = "";
|
|
||||||
|
|
||||||
@Config("runtimeDeps.lazyDependencies")
|
|
||||||
lazyDependencies: Record<string, string> = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从插件 registry 收集到的懒加载依赖,键为包名,值为版本范围
|
|
||||||
*/
|
|
||||||
pluginLazyDependencies: Record<string, string> = {};
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
registryResolver!: NpmRegistryResolver;
|
|
||||||
|
|
||||||
commandRunner: CommandRunner = new DefaultCommandRunner();
|
|
||||||
|
|
||||||
private installPromises = new Map<string, Promise<InstallResult>>();
|
|
||||||
|
|
||||||
collectDependencies(plugins: RuntimeDependencyPluginDefine[]): CollectDependenciesResult {
|
|
||||||
const merged: Record<string, string> = {};
|
|
||||||
const seen: Record<string, Array<{ pluginName: string; range: string }>> = {};
|
|
||||||
|
|
||||||
for (const plugin of plugins) {
|
|
||||||
const deps = plugin.dependPackages || {};
|
|
||||||
for (const [packageName, range] of Object.entries(deps)) {
|
|
||||||
seen[packageName] ||= [];
|
|
||||||
seen[packageName].push({ pluginName: plugin.name, range });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const conflicts: DependencyConflict[] = [];
|
|
||||||
for (const [packageName, ranges] of Object.entries(seen)) {
|
|
||||||
const first = ranges[0]?.range;
|
|
||||||
if (!first) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const conflict = ranges.some(item => !areRangesCompatible(first, item.range));
|
|
||||||
if (conflict) {
|
|
||||||
conflicts.push({ packageName, ranges });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
merged[packageName] = first;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { dependencies: merged, conflicts };
|
|
||||||
}
|
|
||||||
|
|
||||||
async ensureInstalled(options: { plugins: RuntimeDependencyPluginDefine[]; logger?: ILogger }): Promise<InstallResult> {
|
|
||||||
const { plugins, logger: log } = options;
|
|
||||||
const { dependencies, conflicts } = this.resolveDependenciesFromPlugins(plugins);
|
|
||||||
if (conflicts.length > 0) {
|
|
||||||
const conflict = conflicts[0];
|
|
||||||
throw new Error(`动态依赖版本冲突: ${conflict.packageName} => ${conflict.ranges.map(item => `${item.pluginName}:${item.range}`).join(", ")}`);
|
|
||||||
}
|
|
||||||
return await this.ensureDependencies({ dependencies, logger: log });
|
|
||||||
}
|
|
||||||
|
|
||||||
async ensureDependencies(options: { dependencies: Record<string, string>; logger?: ILogger }): Promise<InstallResult> {
|
|
||||||
const { dependencies, logger: log } = options;
|
|
||||||
if (!this.enabled) {
|
|
||||||
return {
|
|
||||||
registryUrl: "",
|
|
||||||
packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!this.autoInstall) {
|
|
||||||
return {
|
|
||||||
registryUrl: "",
|
|
||||||
packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const dependenciesHash = this.createDependenciesHash(dependencies);
|
|
||||||
let installPromise = this.installPromises.get(dependenciesHash);
|
|
||||||
if (installPromise) {
|
|
||||||
const nodeModulesPath = path.join(this.getRuntimeDepsRootDir(), "node_modules");
|
|
||||||
if (!fs.existsSync(nodeModulesPath)) {
|
|
||||||
this.installPromises.delete(dependenciesHash);
|
|
||||||
installPromise = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!installPromise) {
|
|
||||||
installPromise = this.doEnsureInstalled({ dependencies, logger: log }).catch(error => {
|
|
||||||
this.installPromises.delete(dependenciesHash);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
this.installPromises.set(dependenciesHash, installPromise);
|
|
||||||
}
|
|
||||||
return await installPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveDependenciesFromPlugins(plugins: RuntimeDependencyPluginDefine[]): CollectDependenciesResult {
|
|
||||||
const expandedPlugins = plugins.flatMap(plugin => this.resolvePluginDependencies(plugin));
|
|
||||||
return this.collectDependencies(expandedPlugins);
|
|
||||||
}
|
|
||||||
|
|
||||||
async ensureRuntimeDependencies(options: { pluginKeys: string | string[]; logger?: ILogger }): Promise<InstallResult> {
|
|
||||||
const { pluginKeys, logger: log } = options;
|
|
||||||
const keys = Array.isArray(pluginKeys) ? pluginKeys : [pluginKeys];
|
|
||||||
const pluginDefines = keys.map(pluginKey => this.getDefineByPluginKey(pluginKey));
|
|
||||||
if (pluginDefines.every(pluginDefine => !pluginDefine.dependPackages && !pluginDefine.dependPlugins)) {
|
|
||||||
return {
|
|
||||||
registryUrl: "",
|
|
||||||
packageJsonPath: path.join(this.getRuntimeDepsRootDir(), "package.json"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const expandedPluginDefines = pluginDefines.flatMap(pluginDefine => this.resolvePluginDependencies(pluginDefine));
|
|
||||||
return await this.ensureInstalled({ plugins: expandedPluginDefines, logger: log });
|
|
||||||
}
|
|
||||||
|
|
||||||
private async doEnsureInstalled(options: { dependencies: Record<string, string>; logger?: ILogger }): Promise<InstallResult> {
|
|
||||||
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)) {
|
|
||||||
return await import(specifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolved = await this.resolveImportSpecifier(specifier, logger);
|
|
||||||
return await import(pathToFileURL(resolved).href);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async resolveImportSpecifier(specifier: string, logger?: ILogger) {
|
|
||||||
try {
|
|
||||||
return this.resolveRuntimeSpecifier(specifier).resolved;
|
|
||||||
} catch (runtimeError: any) {
|
|
||||||
if (!this.isModuleNotFoundError(runtimeError)) {
|
|
||||||
throw runtimeError;
|
|
||||||
}
|
|
||||||
return await this.resolveMissingRuntimeSpecifier(specifier, runtimeError, logger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async resolveMissingRuntimeSpecifier(specifier: string, runtimeError: any, logger?: ILogger) {
|
|
||||||
const packageName = this.parsePackageName(specifier);
|
|
||||||
const mergedDeps = this.getMergedLazyDependencies();
|
|
||||||
const lazyRange = mergedDeps[packageName];
|
|
||||||
if (!lazyRange) {
|
|
||||||
try {
|
|
||||||
return this.resolveProjectSpecifier(specifier, runtimeError).resolved;
|
|
||||||
} catch {
|
|
||||||
throw new Error(`动态依赖未安装且未配置懒加载版本: ${packageName}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await this.ensureLazyDependency(packageName, logger);
|
|
||||||
return this.resolveRuntimeSpecifier(specifier).resolved;
|
|
||||||
} catch (lazyError: any) {
|
|
||||||
return this.resolveProjectSpecifier(specifier, lazyError).resolved;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isNativeImportSpecifier(specifier: string) {
|
|
||||||
return specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:");
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveRuntimeSpecifier(specifier: string): RuntimeImportResolveResult {
|
|
||||||
const packageName = this.parsePackageName(specifier);
|
|
||||||
const packageJsonPath = path.join(this.getRuntimeDepsRootDir(), "package.json");
|
|
||||||
const require = createRequire(packageJsonPath);
|
|
||||||
const resolved = require.resolve(specifier);
|
|
||||||
return { packageName, resolved };
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveProjectSpecifier(specifier: string, cause?: any): RuntimeImportResolveResult {
|
|
||||||
try {
|
|
||||||
const packageName = this.parsePackageName(specifier);
|
|
||||||
const packageJsonPath = path.resolve("package.json");
|
|
||||||
const require = createRequire(packageJsonPath);
|
|
||||||
const resolved = require.resolve(specifier);
|
|
||||||
return { packageName, resolved };
|
|
||||||
} catch (projectError: any) {
|
|
||||||
if (cause) {
|
|
||||||
projectError.cause = cause;
|
|
||||||
}
|
|
||||||
throw projectError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private parsePackageName(specifier: string) {
|
|
||||||
if (!specifier || specifier.trim() !== specifier) {
|
|
||||||
throw new Error(`动态依赖导入路径无效: ${specifier}`);
|
|
||||||
}
|
|
||||||
const parts = specifier.split("/");
|
|
||||||
if (specifier.startsWith("@")) {
|
|
||||||
if (parts.length < 2 || !parts[0] || !parts[1]) {
|
|
||||||
throw new Error(`动态依赖导入路径无效: ${specifier}`);
|
|
||||||
}
|
|
||||||
return `${parts[0]}/${parts[1]}`;
|
|
||||||
}
|
|
||||||
if (!parts[0]) {
|
|
||||||
throw new Error(`动态依赖导入路径无效: ${specifier}`);
|
|
||||||
}
|
|
||||||
return parts[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ensureLazyDependency(packageName: string, logger?: ILogger) {
|
|
||||||
const range = this.lazyDependencies?.[packageName];
|
|
||||||
if (!range) {
|
|
||||||
throw new Error(`动态依赖未安装且未配置懒加载版本: ${packageName}`);
|
|
||||||
}
|
|
||||||
const dependencies = {
|
|
||||||
[packageName]: range,
|
|
||||||
};
|
|
||||||
await this.ensureDependencies({ dependencies, logger });
|
|
||||||
}
|
|
||||||
|
|
||||||
private isModuleNotFoundError(error: any) {
|
|
||||||
return error?.code === "MODULE_NOT_FOUND" || error?.code === "ERR_MODULE_NOT_FOUND";
|
|
||||||
}
|
|
||||||
|
|
||||||
resolvePluginDependencies(current: RuntimeDependencyPluginDefine): RuntimeDependencyPluginDefine[] {
|
|
||||||
const resolved: RuntimeDependencyPluginDefine[] = [];
|
|
||||||
const visited = new Set<string>();
|
|
||||||
|
|
||||||
const visit = (item: RuntimeDependencyPluginDefine) => {
|
|
||||||
const key = this.buildPluginDependencyKey(item);
|
|
||||||
if (visited.has(key)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
visited.add(key);
|
|
||||||
resolved.push(item);
|
|
||||||
for (const [dependencyName, expectedRange] of Object.entries(item.dependPlugins || {})) {
|
|
||||||
const dependency = this.getDefineByPluginKey(dependencyName, item);
|
|
||||||
if (!isPluginVersionCompatible(dependency, expectedRange)) {
|
|
||||||
throw new Error(`插件依赖版本冲突: ${item.name} 依赖 ${dependencyName}@${expectedRange},当前版本为 ${dependency.version || "未声明"}`);
|
|
||||||
}
|
|
||||||
visit(dependency);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
visit(current);
|
|
||||||
return resolved;
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildPluginDependencyKey(plugin: RuntimeDependencyPluginDefine) {
|
|
||||||
if (plugin.pluginType === "addon" && plugin.addonType) {
|
|
||||||
return `addon:${plugin.addonType}:${plugin.name}`;
|
|
||||||
}
|
|
||||||
const pluginType = plugin.pluginType === "deploy" ? "plugin" : plugin.pluginType || "unknown";
|
|
||||||
return `${pluginType}:${plugin.name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDefineByPluginKey(pluginKey: string, owner?: RuntimeDependencyPluginDefine): RuntimeDependencyPluginDefine {
|
|
||||||
const parts = pluginKey.split(":");
|
|
||||||
const [pluginType, subtype, rawName] = parts;
|
|
||||||
let name = rawName;
|
|
||||||
if (parts.length === 2) {
|
|
||||||
name = subtype;
|
|
||||||
} else if (parts.length === 3) {
|
|
||||||
//无修改
|
|
||||||
} else {
|
|
||||||
const ownerName = owner?.name || pluginKey;
|
|
||||||
throw new Error(`插件依赖格式错误: ${ownerName} 依赖 ${pluginKey},请使用 plugin:name、access:name、notification:name、dnsProvider:name 或 addon:subtype:name 格式`);
|
|
||||||
}
|
|
||||||
const registryMap: Record<string, { registry: Registry<any>; key: string; pluginType: string; addonType?: string }> = {
|
|
||||||
plugin: { registry: pluginRegistry, key: name, pluginType: "plugin" },
|
|
||||||
access: { registry: accessRegistry, key: name, pluginType: "access" },
|
|
||||||
notification: { registry: notificationRegistry, key: name, pluginType: "notification" },
|
|
||||||
dnsProvider: { registry: dnsProviderRegistry, key: name, pluginType: "dnsProvider" },
|
|
||||||
addon: { registry: addonRegistry, key: `${subtype}:${name}`, pluginType: "addon", addonType: subtype },
|
|
||||||
};
|
|
||||||
const target = registryMap[pluginType];
|
|
||||||
if (!target) {
|
|
||||||
const ownerName = owner?.name || pluginKey;
|
|
||||||
throw new Error(`插件依赖格式错误: ${ownerName} 依赖 ${pluginKey},未知插件类型 ${pluginType}`);
|
|
||||||
}
|
|
||||||
const define = target.registry.getDefine(target.key) as RegisteredDefineLike;
|
|
||||||
if (!define) {
|
|
||||||
throw new Error(`插件依赖缺失: ${owner?.name || pluginKey} 依赖 ${pluginKey},但该插件未注册或已禁用`);
|
|
||||||
}
|
|
||||||
return { ...define, key: pluginKey, pluginType: target.pluginType, addonType: target.addonType };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async withInstallLock<T>(run: () => Promise<T>): Promise<T> {
|
|
||||||
const rootDir = this.getRuntimeDepsRootDir();
|
|
||||||
fs.mkdirSync(rootDir, { recursive: true });
|
|
||||||
const lockFile = path.join(rootDir, ".install.lock");
|
|
||||||
const previous = PROCESS_LOCKS.get(lockFile);
|
|
||||||
if (previous) {
|
|
||||||
await previous.catch(() => undefined);
|
|
||||||
}
|
|
||||||
let releaseProcessLock!: () => void;
|
|
||||||
const current = new Promise<void>(resolve => {
|
|
||||||
releaseProcessLock = resolve;
|
|
||||||
});
|
|
||||||
PROCESS_LOCKS.set(lockFile, current);
|
|
||||||
let fd: number | undefined;
|
|
||||||
try {
|
|
||||||
fd = await this.acquireFileLock(lockFile);
|
|
||||||
return await run();
|
|
||||||
} finally {
|
|
||||||
if (fd != null) {
|
|
||||||
fs.closeSync(fd);
|
|
||||||
try {
|
|
||||||
fs.rmSync(lockFile, { force: true });
|
|
||||||
} catch {
|
|
||||||
try {
|
|
||||||
fs.rmSync(lockFile, { force: true });
|
|
||||||
} catch {
|
|
||||||
// Windows 下 closeSync 后文件句柄可能未立即释放,忽略清理失败
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
releaseProcessLock();
|
|
||||||
if (PROCESS_LOCKS.get(lockFile) === current) {
|
|
||||||
PROCESS_LOCKS.delete(lockFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async acquireFileLock(lockFile: string) {
|
|
||||||
const deadline = Date.now() + this.installTimeoutMs;
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
const fd = fs.openSync(lockFile, "wx");
|
|
||||||
// @ts-ignore
|
|
||||||
fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }), "utf8");
|
|
||||||
return fd;
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error?.code !== "EEXIST") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
if (Date.now() > deadline) {
|
|
||||||
throw new Error(`动态依赖安装锁等待超时: ${lockFile}`);
|
|
||||||
}
|
|
||||||
await this.waitForExternalLock(lockFile, deadline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async waitForExternalLock(lockFile: string, deadline: number) {
|
|
||||||
while (fs.existsSync(lockFile)) {
|
|
||||||
if (Date.now() > deadline) {
|
|
||||||
throw new Error(`动态依赖安装锁等待超时: ${lockFile}`);
|
|
||||||
}
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async clearRuntimeDeps() {
|
|
||||||
const rootDir = this.getRuntimeDepsRootDir();
|
|
||||||
const normalizedRootDir = path.normalize(rootDir);
|
|
||||||
if (!normalizedRootDir.endsWith(path.normalize(".runtime-deps"))) {
|
|
||||||
throw new Error(`动态依赖目录配置异常,拒绝清理: ${rootDir}`);
|
|
||||||
}
|
|
||||||
await this.withInstallLock(async () => {
|
|
||||||
if (fs.existsSync(rootDir)) {
|
|
||||||
const entries = fs.readdirSync(rootDir);
|
|
||||||
for (const entry of entries) {
|
|
||||||
if (entry === ".install.lock") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const entryPath = path.join(rootDir, entry);
|
|
||||||
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.installPromises.clear();
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并 package.json 的 lazyDependencies 和插件注册表收集到的 dependPackages
|
|
||||||
* 插件依赖优先(同名时使用插件声明的版本)
|
|
||||||
*/
|
|
||||||
getMergedLazyDependencies(): Record<string, string> {
|
|
||||||
return { ...this.lazyDependencies, ...this.pluginLazyDependencies };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从所有插件注册表中收集 dependPackages,合并到 pluginLazyDependencies
|
|
||||||
*/
|
|
||||||
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> = {};
|
|
||||||
for (const { registry } of registries) {
|
|
||||||
const defineList = registry.getDefineList();
|
|
||||||
for (const define of defineList) {
|
|
||||||
const dependPackages = (define as any).dependPackages as Record<string, string> | undefined;
|
|
||||||
if (!dependPackages) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const [pkgName, range] of Object.entries(dependPackages)) {
|
|
||||||
const existing = deps[pkgName];
|
|
||||||
if (existing && !areRangesCompatible(existing, range)) {
|
|
||||||
logger.warn(`懒加载依赖版本冲突: ${pkgName} => ${existing} vs ${range},保留已有版本`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
deps[pkgName] = range;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pluginLazyDependencies = deps;
|
|
||||||
logger.info(`从插件注册表收集到 ${Object.keys(deps).length} 个懒加载依赖`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新插件懒加载依赖:收集所有插件的 dependPackages 到内存列表,实际安装延迟到 importRuntime 时触发
|
|
||||||
*/
|
|
||||||
async refreshPluginDeps() {
|
|
||||||
this.collectPluginDeps();
|
|
||||||
}
|
|
||||||
|
|
||||||
private readInstallState(statePath: string): any {
|
|
||||||
if (!fs.existsSync(statePath)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private writeInstallState(statePath: string, state: any) {
|
|
||||||
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), "utf8");
|
|
||||||
}
|
|
||||||
|
|
||||||
private readManifestDependencies(packageJsonPath: string): Record<string, string> {
|
|
||||||
if (!fs.existsSync(packageJsonPath)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const manifest = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
||||||
return manifest.dependencies || {};
|
|
||||||
} catch {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private mergeInstalledDependencies(installed: Record<string, string>, requested: Record<string, string>) {
|
|
||||||
const dependencies = { ...installed };
|
|
||||||
for (const [packageName, range] of Object.entries(requested)) {
|
|
||||||
const installedRange = dependencies[packageName];
|
|
||||||
if (installedRange && !areRangesCompatible(installedRange, range)) {
|
|
||||||
throw new Error(`动态依赖版本冲突: ${packageName} => installed:${installedRange}, requested:${range}`);
|
|
||||||
}
|
|
||||||
dependencies[packageName] = installedRange || range;
|
|
||||||
}
|
|
||||||
return dependencies;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
private async getPnpmVersion(command: string, env: NodeJS.ProcessEnv) {
|
|
||||||
const rootDir = this.getRuntimeDepsRootDir();
|
|
||||||
const result = await this.commandRunner.run(command, ["--version"], {
|
|
||||||
cwd: rootDir,
|
|
||||||
timeoutMs: Math.min(this.installTimeoutMs, 10000),
|
|
||||||
env,
|
|
||||||
});
|
|
||||||
if (result.code !== 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return (result.stdout || result.stderr || "").trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private getPnpmCommand() {
|
|
||||||
if (this.pnpmCommand) {
|
|
||||||
return this.pnpmCommand;
|
|
||||||
}
|
|
||||||
return "pnpm";
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildChildEnv(registryUrl: string) {
|
|
||||||
// @ts-ignore
|
|
||||||
const env = { ...process.env };
|
|
||||||
for (const key of ["NODE_OPTIONS", "VSCODE_INSPECTOR_OPTIONS", "NODE_INSPECTOR_PORT", "NODE_DEBUG"]) {
|
|
||||||
if (!env[key]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (key === "NODE_OPTIONS") {
|
|
||||||
env[key] = this.stripDebugNodeOptions(env[key] as string);
|
|
||||||
} else {
|
|
||||||
delete env[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (registryUrl) {
|
|
||||||
env.npm_config_registry = registryUrl;
|
|
||||||
env.pnpm_config_registry = registryUrl;
|
|
||||||
}
|
|
||||||
env.CI = env.CI || "true";
|
|
||||||
env.npm_config_confirm_modules_purge = "false";
|
|
||||||
env.pnpm_config_confirm_modules_purge = "false";
|
|
||||||
return env;
|
|
||||||
}
|
|
||||||
|
|
||||||
private stripDebugNodeOptions(value: string) {
|
|
||||||
return value
|
|
||||||
.split(/\s+/)
|
|
||||||
.filter(Boolean)
|
|
||||||
.filter(item => !/^--inspect(-brk|-port)?(=|$)/.test(item))
|
|
||||||
.filter(item => !/^--debug(=|$)/.test(item))
|
|
||||||
.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
private getRuntimeDepsRootDir() {
|
|
||||||
return path.resolve(this.runtimeDepsRootDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
private createDependenciesHash(dependencies: Record<string, string>) {
|
|
||||||
return crypto.createHash("sha256").update(JSON.stringify(dependencies)).digest("hex");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPluginVersionCompatible(plugin: RuntimeDependencyPluginDefine, expectedRange: string) {
|
|
||||||
if (!expectedRange || expectedRange === "*") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!plugin.version) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return areRangesCompatible(expectedRange, plugin.version);
|
|
||||||
}
|
|
||||||
+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(", ")}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
-347
@@ -1,347 +0,0 @@
|
|||||||
name: nginxProxyManager
|
|
||||||
icon: logos:nginx
|
|
||||||
title: Nginx Proxy Manager 授权
|
|
||||||
group: null
|
|
||||||
desc: 用于登录 Nginx Proxy Manager,并为代理主机证书部署提供授权。
|
|
||||||
setting: null
|
|
||||||
sysSetting: null
|
|
||||||
type: custom
|
|
||||||
disabled: false
|
|
||||||
version: 1.0.0
|
|
||||||
pluginType: access
|
|
||||||
author: samler
|
|
||||||
input:
|
|
||||||
endpoint:
|
|
||||||
title: NPM 地址
|
|
||||||
component:
|
|
||||||
name: a-input
|
|
||||||
allowClear: true
|
|
||||||
placeholder: https://npm.example.com
|
|
||||||
helper: 请输入 Nginx Proxy Manager 根地址,不要带 /api 后缀。
|
|
||||||
required: true
|
|
||||||
email:
|
|
||||||
title: 邮箱
|
|
||||||
component:
|
|
||||||
name: a-input
|
|
||||||
allowClear: true
|
|
||||||
placeholder: admin@example.com
|
|
||||||
required: true
|
|
||||||
password:
|
|
||||||
title: 密码
|
|
||||||
component:
|
|
||||||
name: a-input-password
|
|
||||||
allowClear: true
|
|
||||||
placeholder: 请输入密码
|
|
||||||
required: true
|
|
||||||
encrypt: true
|
|
||||||
totpSecret:
|
|
||||||
title: TOTP 密钥
|
|
||||||
component:
|
|
||||||
name: a-input-password
|
|
||||||
allowClear: true
|
|
||||||
placeholder: Optional base32 TOTP secret
|
|
||||||
helper: 当 Nginx Proxy Manager 账号开启 2FA 时必填。
|
|
||||||
required: false
|
|
||||||
encrypt: true
|
|
||||||
ignoreTls:
|
|
||||||
title: 忽略无效 TLS
|
|
||||||
component:
|
|
||||||
name: a-switch
|
|
||||||
vModel: checked
|
|
||||||
helper: 仅在 Nginx Proxy Manager 使用自签 HTTPS 证书时开启。
|
|
||||||
required: false
|
|
||||||
testRequest:
|
|
||||||
title: 测试
|
|
||||||
component:
|
|
||||||
name: api-test
|
|
||||||
action: TestRequest
|
|
||||||
helper: 测试登录并拉取代理主机列表。
|
|
||||||
content: |
|
|
||||||
const runtimeImport = import;
|
|
||||||
const { BaseAccess } = await runtimeImport("@certd/pipeline");
|
|
||||||
const httpsModule = await runtimeImport("node:https");
|
|
||||||
const { URL } = await runtimeImport("node:url");
|
|
||||||
const axiosModule = await runtimeImport("axios");
|
|
||||||
const formDataModule = await runtimeImport("form-data");
|
|
||||||
|
|
||||||
const https = httpsModule.default ?? httpsModule;
|
|
||||||
const axios = axiosModule.default ?? axiosModule;
|
|
||||||
const FormData = formDataModule.default ?? formDataModule;
|
|
||||||
|
|
||||||
function normalizeEndpoint(endpoint) {
|
|
||||||
const trimmed = String(endpoint ?? "").trim();
|
|
||||||
if (!trimmed) {
|
|
||||||
throw new Error("Nginx Proxy Manager 地址不能为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
const withoutTrailingSlash = trimmed.replace(/\/+$/, "");
|
|
||||||
return withoutTrailingSlash.endsWith("/api")
|
|
||||||
? withoutTrailingSlash.slice(0, -4)
|
|
||||||
: withoutTrailingSlash;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildHttpsAgent(endpoint, ignoreTls) {
|
|
||||||
const url = new URL(endpoint);
|
|
||||||
if (url.protocol !== "https:") {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new https.Agent({
|
|
||||||
rejectUnauthorized: !ignoreTls,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function describeError(error, action) {
|
|
||||||
if (axios.isAxiosError(error)) {
|
|
||||||
const status = error.response?.status;
|
|
||||||
const data = error.response?.data;
|
|
||||||
const message =
|
|
||||||
data?.error?.message ||
|
|
||||||
data?.error ||
|
|
||||||
data?.message ||
|
|
||||||
(typeof data === "string" ? data : null) ||
|
|
||||||
error.message;
|
|
||||||
return new Error(`${action} failed${status ? ` (${status})` : ""}: ${message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof Error) {
|
|
||||||
return new Error(`${action} failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Error(`${action} failed`);
|
|
||||||
}
|
|
||||||
|
|
||||||
class NginxProxyManagerClient {
|
|
||||||
constructor(options) {
|
|
||||||
this.options = options;
|
|
||||||
this.importFn = options.importFn;
|
|
||||||
this.endpoint = normalizeEndpoint(options.endpoint);
|
|
||||||
this.apiBaseUrl = `${this.endpoint}/api`;
|
|
||||||
this.token = undefined;
|
|
||||||
this.tokenPromise = undefined;
|
|
||||||
this.httpClient = axios.create({
|
|
||||||
baseURL: this.apiBaseUrl,
|
|
||||||
timeout: 30000,
|
|
||||||
maxBodyLength: Infinity,
|
|
||||||
maxContentLength: Infinity,
|
|
||||||
httpsAgent: buildHttpsAgent(this.endpoint, options.ignoreTls === true),
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async verifyAccess() {
|
|
||||||
const proxyHosts = await this.getProxyHosts();
|
|
||||||
return {
|
|
||||||
proxyHostCount: proxyHosts.length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async getProxyHosts(searchQuery) {
|
|
||||||
return await this.requestWithAuth({
|
|
||||||
method: "GET",
|
|
||||||
url: "/nginx/proxy-hosts",
|
|
||||||
params: {
|
|
||||||
expand: "certificate",
|
|
||||||
...(searchQuery ? { query: searchQuery } : {}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCertificates(searchQuery) {
|
|
||||||
return await this.requestWithAuth({
|
|
||||||
method: "GET",
|
|
||||||
url: "/nginx/certificates",
|
|
||||||
params: searchQuery ? { query: searchQuery } : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCertificatesWithExpand(searchQuery, expand = []) {
|
|
||||||
return await this.requestWithAuth({
|
|
||||||
method: "GET",
|
|
||||||
url: "/nginx/certificates",
|
|
||||||
params: {
|
|
||||||
...(searchQuery ? { query: searchQuery } : {}),
|
|
||||||
...(expand.length > 0 ? { expand: expand.join(",") } : {}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async findCustomCertificateByNiceName(niceName) {
|
|
||||||
const certificates = await this.getCertificates(niceName);
|
|
||||||
return certificates.find((certificate) => {
|
|
||||||
return certificate.provider === "other" && certificate.nice_name === niceName;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async createCustomCertificate(niceName, domainNames = []) {
|
|
||||||
return await this.requestWithAuth({
|
|
||||||
method: "POST",
|
|
||||||
url: "/nginx/certificates",
|
|
||||||
data: {
|
|
||||||
provider: "other",
|
|
||||||
nice_name: niceName,
|
|
||||||
domain_names: domainNames,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteCertificate(certificateId) {
|
|
||||||
await this.requestWithAuth({
|
|
||||||
method: "DELETE",
|
|
||||||
url: `/nginx/certificates/${certificateId}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async uploadCertificate(certificateId, payload) {
|
|
||||||
const form = new FormData();
|
|
||||||
form.append("certificate", Buffer.from(payload.certificate, "utf8"), {
|
|
||||||
filename: "fullchain.pem",
|
|
||||||
contentType: "application/x-pem-file",
|
|
||||||
});
|
|
||||||
form.append("certificate_key", Buffer.from(payload.certificateKey, "utf8"), {
|
|
||||||
filename: "privkey.pem",
|
|
||||||
contentType: "application/x-pem-file",
|
|
||||||
});
|
|
||||||
if (payload.intermediateCertificate) {
|
|
||||||
form.append("intermediate_certificate", Buffer.from(payload.intermediateCertificate, "utf8"), {
|
|
||||||
filename: "chain.pem",
|
|
||||||
contentType: "application/x-pem-file",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.requestWithAuth({
|
|
||||||
method: "POST",
|
|
||||||
url: `/nginx/certificates/${certificateId}/upload`,
|
|
||||||
data: form,
|
|
||||||
headers: form.getHeaders(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async assignCertificateToProxyHost(hostId, certificateId) {
|
|
||||||
await this.requestWithAuth({
|
|
||||||
method: "PUT",
|
|
||||||
url: `/nginx/proxy-hosts/${hostId}`,
|
|
||||||
data: {
|
|
||||||
certificate_id: certificateId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async login() {
|
|
||||||
if (this.token) {
|
|
||||||
return this.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.tokenPromise) {
|
|
||||||
this.tokenPromise = this.performLogin().finally(() => {
|
|
||||||
this.tokenPromise = undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.token = await this.tokenPromise;
|
|
||||||
return this.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
async performLogin() {
|
|
||||||
const initialLogin = await this.request({
|
|
||||||
method: "POST",
|
|
||||||
url: "/tokens",
|
|
||||||
data: {
|
|
||||||
identity: this.options.email,
|
|
||||||
secret: this.options.password,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (initialLogin.token) {
|
|
||||||
return initialLogin.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!initialLogin.requires_2fa || !initialLogin.challenge_token) {
|
|
||||||
throw new Error("登录失败:Nginx Proxy Manager 未返回访问令牌");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.options.totpSecret) {
|
|
||||||
throw new Error("登录失败:该 Nginx Proxy Manager 账号启用了 2FA,但未配置 totpSecret");
|
|
||||||
}
|
|
||||||
|
|
||||||
let code;
|
|
||||||
try {
|
|
||||||
const { authenticator } = await this.importFn("otplib");
|
|
||||||
code = authenticator.generate(this.options.totpSecret);
|
|
||||||
} catch (error) {
|
|
||||||
throw describeError(error, "Generating TOTP code");
|
|
||||||
}
|
|
||||||
|
|
||||||
const completedLogin = await this.request({
|
|
||||||
method: "POST",
|
|
||||||
url: "/tokens/2fa",
|
|
||||||
data: {
|
|
||||||
challenge_token: initialLogin.challenge_token,
|
|
||||||
code,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!completedLogin.token) {
|
|
||||||
throw new Error("2FA 登录失败:Nginx Proxy Manager 未返回访问令牌");
|
|
||||||
}
|
|
||||||
|
|
||||||
return completedLogin.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
async requestWithAuth(config) {
|
|
||||||
const token = await this.login();
|
|
||||||
const headers = {
|
|
||||||
...(config.headers ?? {}),
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return await this.request({
|
|
||||||
...config,
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async request(config) {
|
|
||||||
const action = `${config.method ?? "GET"} ${config.url ?? "/"}`;
|
|
||||||
try {
|
|
||||||
const response = await this.httpClient.request(config);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
throw describeError(error, action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return class NginxProxyManagerAccess extends BaseAccess {
|
|
||||||
endpoint = "";
|
|
||||||
email = "";
|
|
||||||
password = "";
|
|
||||||
totpSecret = "";
|
|
||||||
ignoreTls = false;
|
|
||||||
testRequest = true;
|
|
||||||
|
|
||||||
createClient() {
|
|
||||||
return new NginxProxyManagerClient({
|
|
||||||
endpoint: this.endpoint,
|
|
||||||
email: this.email,
|
|
||||||
password: this.password,
|
|
||||||
totpSecret: this.totpSecret || undefined,
|
|
||||||
ignoreTls: this.ignoreTls === true,
|
|
||||||
importFn: this.importRuntime.bind(this),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async onTestRequest() {
|
|
||||||
const client = this.createClient();
|
|
||||||
const result = await client.verifyAccess();
|
|
||||||
this.ctx.logger.info(`Nginx Proxy Manager 授权验证成功,找到 ${result.proxyHostCount} 个代理主机`);
|
|
||||||
return `成功(${result.proxyHostCount} 个代理主机)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getProxyHostList(req = {}) {
|
|
||||||
const client = this.createClient();
|
|
||||||
return await client.getProxyHosts(req.searchKey);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -121,7 +121,7 @@ export class DingTalkNotification extends BaseNotification {
|
|||||||
at.isAtAll = true;
|
at.isAtAll = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const color = body.errorMessage ? "red" : "green";
|
const color = body.errorMessage ? "#CC0000": "#00CC66";
|
||||||
const res = await this.http.request({
|
const res = await this.http.request({
|
||||||
url: webhook,
|
url: webhook,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,11 @@ export class VolcengineAccess extends BaseAccess {
|
|||||||
testRequest = true;
|
testRequest = true;
|
||||||
|
|
||||||
async onTestRequest() {
|
async onTestRequest() {
|
||||||
await this.getCallerIdentity();
|
const result = await this.getCallerIdentity();
|
||||||
|
this.ctx.logger.info("✅ 密钥有效!");
|
||||||
|
this.ctx.logger.info(` 账户ID: ${result.accountId}`);
|
||||||
|
this.ctx.logger.info(` ARN: ${result.arn}`);
|
||||||
|
this.ctx.logger.info(` 用户ID: ${result.userId}`);
|
||||||
return "ok";
|
return "ok";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,18 +64,18 @@ export class VolcengineAccess extends BaseAccess {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = res.Result || {};
|
const result = res.Result || {};
|
||||||
this.ctx.logger.info("✅ 密钥有效!");
|
|
||||||
this.ctx.logger.info(` 账户ID: ${result.AccountId}`);
|
|
||||||
this.ctx.logger.info(` ARN: ${result.Trn}`);
|
|
||||||
this.ctx.logger.info(` 用户ID: ${result.IdentityId}`);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
valid: true,
|
valid: true,
|
||||||
accountId: result.AccountId,
|
accountId: result.AccountId,
|
||||||
arn: result.Trn,
|
arn: result.Trn,
|
||||||
userId: result.IdentityId,
|
userId: parseInt(result.IdentityId),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUserId() {
|
||||||
|
const { userId } = await this.getCallerIdentity();
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new VolcengineAccess();
|
new VolcengineAccess();
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
+17
-11
@@ -19,16 +19,23 @@ describe("VolcengineDeployToVKE", () => {
|
|||||||
plugin.kubeconfigType = "Public";
|
plugin.kubeconfigType = "Public";
|
||||||
plugin.logger = { info: () => undefined } as any;
|
plugin.logger = { info: () => undefined } as any;
|
||||||
|
|
||||||
let requestBody: any;
|
let createRequestBody: any;
|
||||||
const kubeconfigId = await (plugin as any).createKubeconfig({
|
const kubeconfig = ["apiVersion: v1", "clusters:", "- cluster:", " server: https://example.com", " name: vke", "contexts: []", "current-context: vke"].join("\n");
|
||||||
|
const vkeService = {
|
||||||
request: async (req: any) => {
|
request: async (req: any) => {
|
||||||
requestBody = req.body;
|
if (req.action === "CreateKubeconfig") {
|
||||||
|
createRequestBody = req.body;
|
||||||
return { Result: { Id: "kc-123" } };
|
return { Result: { Id: "kc-123" } };
|
||||||
|
} else if (req.action === "ListKubeconfigs") {
|
||||||
|
return { Result: { Items: [{ Id: "kc-123", Kubeconfig: Buffer.from(kubeconfig).toString("base64") }] } };
|
||||||
|
}
|
||||||
|
return {};
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
const kubeconfigStr = await (plugin as any).createKubeconfig({ vkeService });
|
||||||
|
|
||||||
assert.equal(kubeconfigId, "kc-123");
|
assert.equal(createRequestBody.ClusterId, "cc1234567890123456789");
|
||||||
assert.equal(requestBody.ClusterId, "cc1234567890123456789");
|
assert.equal(kubeconfigStr, kubeconfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("decodes the base64 kubeconfig returned by VKE", async () => {
|
it("decodes the base64 kubeconfig returned by VKE", async () => {
|
||||||
@@ -38,16 +45,15 @@ describe("VolcengineDeployToVKE", () => {
|
|||||||
|
|
||||||
const kubeconfig = ["apiVersion: v1", "clusters:", "- cluster:", " server: https://example.com", " name: vke", "contexts: []", "current-context: vke"].join("\n");
|
const kubeconfig = ["apiVersion: v1", "clusters:", "- cluster:", " server: https://example.com", " name: vke", "contexts: []", "current-context: vke"].join("\n");
|
||||||
|
|
||||||
const result = await (plugin as any).getKubeconfig(
|
const vkeService = {
|
||||||
{
|
|
||||||
request: async () => ({
|
request: async () => ({
|
||||||
Result: {
|
Result: {
|
||||||
Items: [{ Id: "kc-123", Kubeconfig: Buffer.from(kubeconfig).toString("base64") }],
|
Items: [{ Id: "kc-123", Kubeconfig: Buffer.from(kubeconfig).toString("base64") }],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
};
|
||||||
"kc-123"
|
|
||||||
);
|
const result = await (plugin as any).getKubeconfig({ vkeService, kubeconfigId: "kc-123" });
|
||||||
|
|
||||||
assert.equal(result, kubeconfig);
|
assert.equal(result, kubeconfig);
|
||||||
});
|
});
|
||||||
|
|||||||
+71
-42
@@ -208,10 +208,10 @@ export class VolcengineDeployToVKE extends AbstractPlusTaskPlugin {
|
|||||||
this.logger.info("使用已有证书中心ID:" + certId);
|
this.logger.info("使用已有证书中心ID:" + certId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const kubeconfigId = await this.createKubeconfig(vkeService);
|
const userId = await access.getUserId();
|
||||||
|
this.logger.info(`当前用户ID:${userId}`);
|
||||||
try {
|
const kubeconfig = await this.getOrCreateKubeconfig({ vkeService, userId });
|
||||||
const kubeconfig = await this.getKubeconfig(vkeService, kubeconfigId);
|
try{
|
||||||
const k8sClient = new this.K8sClient({
|
const k8sClient = new this.K8sClient({
|
||||||
kubeConfigStr: kubeconfig,
|
kubeConfigStr: kubeconfig,
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
@@ -224,8 +224,6 @@ export class VolcengineDeployToVKE extends AbstractPlusTaskPlugin {
|
|||||||
throw new Error(this.formatK8sError(e.response.body));
|
throw new Error(this.formatK8sError(e.response.body));
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
|
||||||
await this.deleteKubeconfig(vkeService, kubeconfigId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await utils.sleep(5000);
|
await utils.sleep(5000);
|
||||||
@@ -250,7 +248,19 @@ export class VolcengineDeployToVKE extends AbstractPlusTaskPlugin {
|
|||||||
return await client.getVkeService({ region: this.regionId });
|
return await client.getVkeService({ region: this.regionId });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createKubeconfig(vkeService: any) {
|
async getOrCreateKubeconfig(opts: {vkeService: any, userId: number }) : Promise<string> {
|
||||||
|
// 先查询
|
||||||
|
let kubeconfig = await this.getKubeconfig(opts);
|
||||||
|
// 如果不存在,再创建
|
||||||
|
if (!kubeconfig) {
|
||||||
|
return await this.createKubeconfig(opts);
|
||||||
|
}
|
||||||
|
return kubeconfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async createKubeconfig(opts: {vkeService: any}) {
|
||||||
|
const {vkeService} = opts;
|
||||||
const clusterId = this.getClusterId();
|
const clusterId = this.getClusterId();
|
||||||
const res = await vkeService.request({
|
const res = await vkeService.request({
|
||||||
action: "CreateKubeconfig",
|
action: "CreateKubeconfig",
|
||||||
@@ -258,7 +268,7 @@ export class VolcengineDeployToVKE extends AbstractPlusTaskPlugin {
|
|||||||
body: {
|
body: {
|
||||||
ClusterId: clusterId,
|
ClusterId: clusterId,
|
||||||
Type: this.kubeconfigType,
|
Type: this.kubeconfigType,
|
||||||
ValidDuration: 3600,
|
ValidDuration: 26280, //3年
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const kubeconfigId = res.Result?.Id || res.Id;
|
const kubeconfigId = res.Result?.Id || res.Id;
|
||||||
@@ -266,25 +276,49 @@ export class VolcengineDeployToVKE extends AbstractPlusTaskPlugin {
|
|||||||
throw new Error(`生成VKE Kubeconfig失败:${JSON.stringify(res)}`);
|
throw new Error(`生成VKE Kubeconfig失败:${JSON.stringify(res)}`);
|
||||||
}
|
}
|
||||||
this.logger.info(`已生成临时Kubeconfig:${kubeconfigId}`);
|
this.logger.info(`已生成临时Kubeconfig:${kubeconfigId}`);
|
||||||
return kubeconfigId;
|
|
||||||
|
const config = await this.getKubeconfig({ vkeService, kubeconfigId });
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getKubeconfig(vkeService: any, kubeconfigId: string) {
|
private async getKubeconfig(opts: {vkeService: any, kubeconfigId?: string, userId?:number }) {
|
||||||
|
const {vkeService, kubeconfigId, userId} = opts;
|
||||||
const clusterId = this.getClusterId();
|
const clusterId = this.getClusterId();
|
||||||
|
const query: any = {
|
||||||
|
ClusterIds: [clusterId],
|
||||||
|
Types: [this.kubeconfigType],
|
||||||
|
}
|
||||||
|
if (kubeconfigId) {
|
||||||
|
query.Ids = [kubeconfigId];
|
||||||
|
}
|
||||||
|
if (userId) {
|
||||||
|
query.UserIds = [userId];
|
||||||
|
}
|
||||||
const res = await vkeService.request({
|
const res = await vkeService.request({
|
||||||
action: "ListKubeconfigs",
|
action: "ListKubeconfigs",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
Filter: {
|
Filter: query,
|
||||||
ClusterIds: [clusterId],
|
|
||||||
Ids: [kubeconfigId],
|
|
||||||
Types: [this.kubeconfigType],
|
|
||||||
},
|
|
||||||
PageNumber: 1,
|
PageNumber: 1,
|
||||||
PageSize: 10,
|
PageSize: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const items = res.Result?.Items || res.Items || [];
|
let items = res.Result?.Items || res.Items || [];
|
||||||
|
if (items.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const now = new Date();
|
||||||
|
items = items.filter((it: any) => {
|
||||||
|
if (!it.ExpireTime) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const expireTime = new Date(it.ExpireTime);
|
||||||
|
return expireTime > now;
|
||||||
|
});
|
||||||
|
if (items.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const item = items.find((it: any) => it.Id === kubeconfigId) || items[0];
|
const item = items.find((it: any) => it.Id === kubeconfigId) || items[0];
|
||||||
const kubeconfig = item?.Kubeconfig;
|
const kubeconfig = item?.Kubeconfig;
|
||||||
if (!kubeconfig) {
|
if (!kubeconfig) {
|
||||||
@@ -293,25 +327,25 @@ export class VolcengineDeployToVKE extends AbstractPlusTaskPlugin {
|
|||||||
return this.decodeKubeconfig(kubeconfig);
|
return this.decodeKubeconfig(kubeconfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async deleteKubeconfig(vkeService: any, kubeconfigId?: string) {
|
// private async deleteKubeconfig(vkeService: any, kubeconfigId?: string) {
|
||||||
if (!kubeconfigId) {
|
// if (!kubeconfigId) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
const clusterId = this.getClusterId();
|
// const clusterId = this.getClusterId();
|
||||||
try {
|
// try {
|
||||||
await vkeService.request({
|
// await vkeService.request({
|
||||||
action: "DeleteKubeconfigs",
|
// action: "DeleteKubeconfigs",
|
||||||
method: "POST",
|
// method: "POST",
|
||||||
body: {
|
// body: {
|
||||||
ClusterId: clusterId,
|
// ClusterId: clusterId,
|
||||||
Ids: [kubeconfigId],
|
// Ids: [kubeconfigId],
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
this.logger.info(`已删除临时Kubeconfig:${kubeconfigId}`);
|
// this.logger.info(`已删除临时Kubeconfig:${kubeconfigId}`);
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
this.logger.warn(`删除临时Kubeconfig失败:${e.message || e}`);
|
// this.logger.warn(`删除临时Kubeconfig失败:${e.message || e}`);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
private getClusterId() {
|
private getClusterId() {
|
||||||
if (!this.clusterId) {
|
if (!this.clusterId) {
|
||||||
@@ -476,10 +510,8 @@ export class VolcengineDeployToVKE extends AbstractPlusTaskPlugin {
|
|||||||
}
|
}
|
||||||
const access = await this.getAccess<VolcengineAccess>(this.accessId);
|
const access = await this.getAccess<VolcengineAccess>(this.accessId);
|
||||||
const vkeService = await this.getVkeService(access);
|
const vkeService = await this.getVkeService(access);
|
||||||
const kubeconfigId = await this.createKubeconfig(vkeService);
|
const userId = await access.getUserId();
|
||||||
|
const kubeconfig = await this.getOrCreateKubeconfig({ vkeService, userId });
|
||||||
try {
|
|
||||||
const kubeconfig = await this.getKubeconfig(vkeService, kubeconfigId);
|
|
||||||
const k8sClient = new this.K8sClient({
|
const k8sClient = new this.K8sClient({
|
||||||
kubeConfigStr: kubeconfig,
|
kubeConfigStr: kubeconfig,
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
@@ -491,9 +523,6 @@ export class VolcengineDeployToVKE extends AbstractPlusTaskPlugin {
|
|||||||
label: item.metadata.name,
|
label: item.metadata.name,
|
||||||
value: item.metadata.name,
|
value: item.metadata.name,
|
||||||
}));
|
}));
|
||||||
} finally {
|
|
||||||
await this.deleteKubeconfig(vkeService, kubeconfigId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+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),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user