perf: 支持网络测试

v2-dev
xiaojunnuo 2025-09-30 23:27:31 +08:00
parent aee13ad909
commit 2bef608e07
12 changed files with 916 additions and 61 deletions

View File

@ -1,8 +1,8 @@
//转换为import
import childProcess from 'child_process';
import { safePromise } from './util.promise.js';
import { ILogger, logger } from './util.log.js';
import iconv from 'iconv-lite';
import childProcess from "child_process";
import { safePromise } from "./util.promise.js";
import { ILogger, logger } from "./util.log.js";
import iconv from "iconv-lite";
export type ExecOption = {
cmd: string | string[];
env: any;
@ -11,12 +11,12 @@ export type ExecOption = {
};
async function exec(opts: ExecOption): Promise<string> {
let cmd = '';
let cmd = "";
const log = opts.logger || logger;
if (opts.cmd instanceof Array) {
for (const item of opts.cmd) {
if (cmd) {
cmd += ' && ' + item;
cmd += " && " + item;
} else {
cmd = item;
}
@ -38,7 +38,7 @@ async function exec(opts: ExecOption): Promise<string> {
log.error(`exec error: ${error}`);
reject(error);
} else {
const res = stdout.toString('utf-8');
const res = stdout.toString("utf-8");
log.info(`stdout: ${res}`);
resolve(res);
}
@ -57,11 +57,11 @@ export type SpawnOption = {
};
function isWindows() {
return process.platform === 'win32';
return process.platform === "win32";
}
function convert(buffer: any) {
if (isWindows()) {
const decoded = iconv.decode(buffer, 'GBK');
const decoded = iconv.decode(buffer, "GBK");
// 检查是否有有效字符
return decoded && decoded.trim().length > 0 ? decoded : buffer.toString();
} else {
@ -74,12 +74,12 @@ function convert(buffer: any) {
// }
async function spawn(opts: SpawnOption): Promise<string> {
let cmd = '';
let cmd = "";
const log = opts.logger || logger;
if (opts.cmd instanceof Array) {
for (const item of opts.cmd) {
if (cmd) {
cmd += ' && ' + item;
cmd += " && " + item;
} else {
cmd = item;
}
@ -88,8 +88,8 @@ async function spawn(opts: SpawnOption): Promise<string> {
cmd = opts.cmd;
}
log.info(`执行命令: ${cmd}`);
let stdout = '';
let stderr = '';
let stdout = "";
let stderr = "";
return safePromise((resolve, reject) => {
const ls = childProcess.spawn(cmd, {
shell: true,
@ -99,23 +99,23 @@ async function spawn(opts: SpawnOption): Promise<string> {
},
...opts.options,
});
ls.stdout.on('data', data => {
ls.stdout.on("data", data => {
data = convert(data);
log.info(`stdout: ${data}`);
stdout += data;
});
ls.stderr.on('data', data => {
ls.stderr.on("data", data => {
data = convert(data);
log.warn(`stderr: ${data}`);
stderr += data;
});
ls.on('error', error => {
ls.on("error", error => {
log.error(`child process error: ${error}`);
reject(error);
});
ls.on('close', (code: number) => {
ls.on("close", (code: number) => {
if (code !== 0) {
log.error(`child process exited with code ${code}`);
reject(new Error(stderr));

View File

@ -175,6 +175,7 @@ export default {
suiteSetting: "Suite Settings",
orderManager: "Order Management",
userSuites: "User Suites",
netTest: "Network Test",
},
certificateRepo: {
title: "Certificate Repository",

View File

@ -181,6 +181,7 @@ export default {
suiteSetting: "套餐设置",
orderManager: "订单管理",
userSuites: "用户套餐",
netTest: "网络测试",
},
certificateRepo: {
title: "证书仓库",

View File

@ -249,6 +249,17 @@ export const sysResources = [
},
],
},
{
title: "certd.sysResources.netTest",
name: "NetTest",
path: "/sys/nettest",
component: "/sys/nettest/index.vue",
meta: {
icon: "ion:build-outline",
auth: true,
keepAlive: true,
},
},
],
},
];

View File

@ -0,0 +1,186 @@
<template>
<div class="domain-test-card">
<div class="card-header">
<a-form v-if="editing" layout="inline" :model="formData">
<a-form-item label="域名">
<a-input v-model:value="formData.domain" placeholder="请输入要测试的域名或IP" style="width: 240px" />
</a-form-item>
<a-form-item label="端口">
<a-input-number v-model:value="formData.port" placeholder="请输入端口" :min="1" :max="65535" style="width: 120px" />
</a-form-item>
</a-form>
<div v-else class="domain-info">
<span>域名: {{ formData.domain }}</span>
<span>端口: {{ formData.port }}</span>
</div>
<a-button :disabled="!formData.domain" size="small" type="primary" :loading="loading" @click="runAllTests"> </a-button>
</div>
<div class="card-content">
<div class="test-results">
<!-- 域名解析结果 -->
<test-case ref="domainResolveRef" title="域名解析" :test-method="() => createDomainResolveMethod()" :disabled="!getCurrentDomain()" />
<!-- Ping测试结果 -->
<test-case ref="pingTestRef" title="Ping测试" :test-method="() => createPingTestMethod()" :disabled="!getCurrentDomain()" />
<!-- Telnet测试结果 -->
<test-case ref="telnetTestRef" title="Telnet测试" :port="getCurrentPort()" :test-method="() => createTelnetTestMethod()" :disabled="!getCurrentDomain() || !getCurrentPort()" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, onMounted } from "vue";
import { message } from "ant-design-vue";
import { DomainResolve, PingTest, TelnetTest } from "./api";
import TestCase from "./TestCase.vue";
//
const props = defineProps<{
domain?: string;
port?: number;
autoStart?: boolean;
}>();
const editing = ref(!props.domain);
//
const domainResolveRef = ref();
const pingTestRef = ref();
const telnetTestRef = ref();
//
const formData = reactive({
domain: props.domain || "",
port: props.port || 443,
});
//
const loading = ref(false);
//
const createDomainResolveMethod = async () => {
const domain = getCurrentDomain();
return DomainResolve(domain);
};
// Ping
const createPingTestMethod = async () => {
const domain = getCurrentDomain();
return PingTest(domain);
};
// Telnet
const createTelnetTestMethod = async () => {
const domain = getCurrentDomain();
const port = getCurrentPort();
return TelnetTest(domain, port);
};
// 使
const getCurrentDomain = () => {
return formData.domain;
};
// 使
const getCurrentPort = () => {
return formData.port;
};
//
async function runAllTests() {
const domain = getCurrentDomain();
//
if (!domain) {
message.error("请输入域名");
return;
}
loading.value = true;
//
try {
await Promise.allSettled([domainResolveRef.value?.test(), pingTestRef.value?.test(), telnetTestRef.value?.test()]);
message.success("所有测试已完成");
} catch (error) {
message.error("部分测试执行失败,请查看详细结果");
} finally {
loading.value = false;
}
}
onMounted(() => {
if (props.autoStart) {
runAllTests();
}
});
</script>
<style lang="less" scoped>
.domain-test-card {
border: 1px solid #e8e8e8;
border-radius: 4px;
overflow: hidden;
background-color: #fff;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background-color: #fafafa;
border-bottom: 1px solid #e8e8e8;
}
.card-header h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
}
.card-content {
padding: 16px;
}
.input-form {
margin-bottom: 12px;
padding: 12px;
background-color: #fafafa;
border-radius: 4px;
}
.domain-info {
padding: 5.5px 12px;
background-color: #f0f0f0;
border-radius: 4px;
display: flex;
gap: 16px;
font-size: 14px;
color: #666;
}
.test-buttons {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
.test-results {
margin-top: 0px;
}
/* 调整按钮大小 */
.ant-btn {
font-size: 12px;
padding: 2px 8px;
height: 24px;
}
</style>

View File

@ -0,0 +1,146 @@
<template>
<a-card title="服务端信息" class="server-info-card">
<template #extra>
<a-button size="small" :loading="loading" @click="refreshServerInfo">
<template #icon>
<a-icon type="sync" :spin="loading" />
</template>
刷新
</a-button>
</template>
<div v-if="loading" class="loading">
<a-spin size="small" />
<span style="margin-left: 8px">加载中...</span>
</div>
<div v-else-if="error" class="error">
<a-alert message="获取服务器信息失败" :description="error" type="error" show-icon />
</div>
<div v-else class="server-info-grid">
<!-- 本地IP -->
<div class="info-item">
<div class="info-label">本地IP:</div>
<div v-if="serverInfo.localIP" class="info-value">
<a-list item-layout="horizontal" :data-source="serverInfo.localIP">
<template #renderItem="{ item }">
<a-list-item>
<a-list-item-meta description="{{ item }}" />
</a-list-item>
</template>
</a-list>
</div>
<div v-else class="info-empty">暂无信息</div>
</div>
<!-- 外网IP -->
<div class="info-item">
<div class="info-label">外网IP:</div>
<div v-if="serverInfo.publicIP" class="info-value">
{{ serverInfo.publicIP }}
</div>
<div v-else class="info-empty">暂无信息</div>
</div>
<!-- DNS服务器 -->
<div class="info-item">
<div class="info-label">DNS服务器:</div>
<div v-if="serverInfo.dnsServers && serverInfo.dnsServers.length > 0" class="info-value">
{{ serverInfo.dnsServers.join(", ") }}
</div>
<div v-else class="info-empty">暂无信息</div>
</div>
</div>
</a-card>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { message } from "ant-design-vue";
import { GetServerInfo } from "./api";
//
interface ServerInfo {
localIP?: string[];
publicIP?: string;
dnsServers?: string[];
}
const loading = ref(false);
const error = ref<string | null>(null);
const serverInfo = ref<ServerInfo>({});
//
const loadServerInfo = async () => {
loading.value = true;
error.value = null;
try {
serverInfo.value = await GetServerInfo();
} catch (e) {
error.value = e instanceof Error ? e.message : String(e);
message.error("获取服务器信息失败");
} finally {
loading.value = false;
}
};
//
const refreshServerInfo = () => {
loadServerInfo();
};
//
onMounted(() => {
loadServerInfo();
});
</script>
<style lang="less">
.server-info-card {
margin-bottom: 16px;
.loading {
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
color: #666;
}
.error {
margin: 0;
}
.server-info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
}
.info-item {
background-color: #fafafa;
border-radius: 4px;
padding: 12px;
.info-label {
font-size: 14px;
font-weight: 500;
color: #666;
margin-bottom: 8px;
}
.info-value {
font-size: 14px;
color: #333;
.ant-list-item {
padding: 4px 0;
}
}
.info-empty {
font-size: 14px;
color: #999;
font-style: italic;
}
}
}
</style>

View File

@ -0,0 +1,185 @@
<template>
<div class="test-case" :class="{ loading }">
<div class="case-header">
<span class="flex items-center">
<fs-button size="small" type="text" icon="ion:play-circle" :loading="loading" :disabled="disabled" class="test-button" @click="runTest" />
<a-tag color="blue" class="case-title">
{{ title }}
</a-tag>
<span v-if="port" class="port-info">{{ port }}</span>
</span>
<span v-if="result && isNetTestResult" class="result-status flex-1" :style="{ color: isSuccess ? 'green' : 'red' }">
<span>
{{ isSuccess ? "✓" : "✗" }}
</span>
<span class="ml-2">
{{ result.message }}
</span>
</span>
</div>
<div v-if="result" class="result-content">
<div v-if="error" class="error-message">
<span style="color: red">{{ error }}</span>
</div>
<div v-else-if="isNetTestResult">
<div v-if="resultTestLog" class="test-log">
<pre>{{ resultTestLog }}</pre>
</div>
</div>
<div v-else-if="typeof result === 'object'" class="object-result">
<pre>{{ JSON.stringify(result, null, 2) }}</pre>
</div>
<div v-else class="text-result">
<pre>{{ result }}</pre>
</div>
</div>
<div v-else class="no-result">
<p>暂无结果</p>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { message } from "ant-design-vue";
//
const props = defineProps<{
title: string;
port?: number | string;
testMethod: () => Promise<any>;
disabled?: boolean;
}>();
//
const loading = ref(false);
const result = ref<any>(null);
const error = ref<string | null>(null);
//
const runTest = async () => {
loading.value = true;
error.value = null;
result.value = null;
try {
const testResult = await props.testMethod();
// data 使 data使
result.value = testResult.data || testResult;
} catch (err: any) {
result.value = null;
error.value = err.message || "测试失败";
message.error(`${props.title} 测试失败: ${error.value}`);
} finally {
loading.value = false;
}
};
//
defineExpose({
test: runTest,
});
//
const isNetTestResult = computed(() => {
return typeof result.value === "object" && result.value !== null && "success" in result.value && "message" in result.value && "testLog" in result.value;
});
const isSuccess = computed(() => {
return isNetTestResult.value && result.value.success;
});
const resultMessage = computed(() => {
return isNetTestResult.value ? result.value.message : "";
});
const resultTestLog = computed(() => {
return isNetTestResult.value ? result.value.testLog : "";
});
const resultError = computed(() => {
return isNetTestResult.value ? result.value.error : "";
});
</script>
<style lang="less" scoped>
.test-case {
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
position: relative;
&:last-child {
border-bottom: none;
}
&.loading {
opacity: 0.7;
}
}
.case-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.result-status {
font-size: 14px;
color: #999;
margin-right: 10px;
}
}
.case-title {
font-weight: 500;
font-size: 14px;
}
.port-info {
font-size: 12px;
color: #999;
background-color: #f0f0f0;
padding: 2px 6px;
border-radius: 3px;
margin-right: 8px;
}
.test-button {
color: #1890ff;
font-size: 12px;
margin-right: 5px;
}
.result-content {
.error-message,
.object-result,
.text-result {
background-color: #f8f8f8;
padding: 8px 10px;
border-radius: 3px;
overflow-x: auto;
}
pre {
margin: 0;
font-size: 12px;
line-height: 1.4;
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
white-space: pre-wrap;
word-wrap: break-word;
}
.test-log {
background-color: #f8f8f8;
padding: 8px 10px;
border-radius: 3px;
overflow-x: auto;
}
}
.no-result {
padding: 12px 0;
text-align: center;
color: #999;
font-size: 12px;
}
</style>

View File

@ -0,0 +1,33 @@
import { request } from "/@/api/service";
export async function DomainResolve(domain: string) {
return await request({
url: "/sys/nettest/domainResolve",
method: "post",
data: { domain },
});
}
export async function PingTest(domain: string) {
return await request({
url: "/sys/nettest/ping",
method: "post",
data: { domain },
});
}
export async function TelnetTest(domain: string, port: number) {
return await request({
url: "/sys/nettest/telnet",
method: "post",
data: { domain, port },
});
}
// 获取服务器信息包括本地IP、外网IP和DNS服务器
export async function GetServerInfo() {
return await request({
url: "/sys/nettest/serverInfo",
method: "post",
});
}

View File

@ -0,0 +1,40 @@
<template>
<fs-page class="page-sys-nettest">
<template #header>
<div class="title">网络测试</div>
</template>
<div class="nettest-container">
<!-- 服务端信息 -->
<server-info-card />
<!-- 测试区域 -->
<div class="test-areas">
<!-- 用户输入域名测试 -->
<domain-test-card class="w-50%" />
<!-- 百度域名测试 (用于对比) -->
<domain-test-card class="w-50%" :domain="'baidu.com'" :port="443" :auto-start="true" />
</div>
</div>
</fs-page>
</template>
<script lang="ts" setup>
import DomainTestCard from "./DomainTestCard.vue";
import ServerInfoCard from "./ServerInfoCard.vue";
</script>
<style lang="less">
.page-sys-nettest {
.nettest-container {
padding: 16px;
background-color: #fff;
}
.test-areas {
display: flex;
gap: 16px;
margin-top: 16px;
}
}
</style>

View File

@ -0,0 +1,47 @@
import { BaseController } from '@certd/lib-server';
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
import { NetTestService } from '../../../modules/sys/nettest/nettest-service.js';
@Provide()
@Controller('/api/sys/nettest/')
export class SysNetTestController extends BaseController {
@Inject()
netTestService: NetTestService;
@Post('/domainResolve', { summary: 'sys:settings:view' })
public async domainResolve(@Body(ALL) body: { domain: string }) {
const { domain } = body;
const result = await this.netTestService.domainResolve(domain);
return this.ok(result);
}
// ping
@Post('/ping', { summary: 'sys:settings:view' })
public async ping(@Body(ALL) body: { domain: string }) {
const { domain } = body;
const result = await this.netTestService.ping(domain);
return this.ok(result);
}
// telnet
@Post('/telnet', { summary: 'sys:settings:view' })
public async telnet(@Body(ALL) body: { domain: string, port: number }) {
const { domain, port } = body;
const result = await this.netTestService.telnet(domain, port);
return this.ok(result);
}
// telnet
@Post('/serverInfo', { summary: 'sys:settings:view' })
public async serverInfo() {
const result = await this.netTestService.serverInfo();
return this.ok(result);
}
}

View File

@ -0,0 +1,208 @@
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { http, utils } from '@certd/basic';
// 使用@certd/basic包中已有的utils.sp.spawn函数替代自定义的asyncExec
// 该函数已经内置了Windows系统编码问题的解决方案
export type NetTestResult = {
success: boolean; //是否成功
message: string; //结果
testLog: string; //测试日志
error?: string; //执行错误信息
}
@Provide('nettestService')
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class NetTestService {
/**
* Telnet
* @param domain
* @param port
* @returns
*/
async telnet(domain: string, port: number): Promise<NetTestResult> {
try {
let command = '';
if (this.isWindows()) {
// Windows系统使用PowerShell执行测试避免输入重定向问题
// 使用PowerShell的Test-NetConnection命令进行端口测试
command = `powershell -Command "& { $result = Test-NetConnection -ComputerName ${domain} -Port ${port} -InformationLevel Quiet; if ($result) { Write-Host '端口连接成功' } else { Write-Host '端口连接失败' } }"`;
} else {
// Linux系统使用nc命令进行端口测试
command = `nc -zv -w 5 ${domain} ${port} 2>&1`;
}
// 使用utils.sp.spawn执行命令它会自动处理Windows编码问题
const output = await utils.sp.spawn({
cmd: command,
logger: undefined // 可以根据需要传入logger
});
// 判断测试是否成功
const success = this.isWindows()
? output.includes('端口连接成功')
: output.includes('succeeded') || output.includes('open');
// 处理结果
return {
success,
message: success ? '端口连接测试成功' : '端口连接测试失败',
testLog: output,
};
} catch (error) {
return {
success: false,
message: 'Telnet测试执行失败',
testLog: error instanceof Error ? error.message : String(error),
error: error instanceof Error ? error.message : String(error)
};
}
}
/**
* Ping
* @param domain
* @returns
*/
async ping(domain: string): Promise<NetTestResult> {
try {
let command = '';
if (this.isWindows()) {
// Windows系统ping命令发送4个包
command = `ping -n 4 ${domain}`;
} else {
// Linux系统ping命令发送4个包
command = `ping -c 4 ${domain}`;
}
// 使用utils.sp.spawn执行命令
const output = await utils.sp.spawn({
cmd: command,
logger: undefined
});
// 判断测试是否成功
const success = this.isWindows()
? output.includes('TTL=')
: output.includes('0% packet loss');
return {
success,
message: success ? 'Ping测试成功' : 'Ping测试失败',
testLog: output,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
message: 'Ping测试执行失败',
testLog: errorMessage,
error: errorMessage
};
}
}
private isWindows() {
return process.platform === 'win32';
}
/**
*
* @param domain
* @returns
*/
async domainResolve(domain: string): Promise<NetTestResult> {
try {
let command = '';
if (this.isWindows()) {
// Windows系统使用nslookup命令
command = `nslookup ${domain}`;
} else {
// Linux系统优先使用dig命令如果没有则回退到nslookup
command = `which dig > /dev/null && dig ${domain} || nslookup ${domain}`;
}
// 使用utils.sp.spawn执行命令
const output = await utils.sp.spawn({
cmd: command,
logger: undefined
});
// 判断测试是否成功
const success = output.includes('Address:') || output.includes('IN A') ||
(this.isWindows() && output.includes('Name:'));
return {
success,
message: success ? '域名解析测试成功' : '域名解析测试失败',
testLog: output,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
message: '域名解析测试执行失败',
testLog: errorMessage,
error: errorMessage
};
}
}
async getLocalIP(): Promise<string> {
try {
const output = await utils.sp.spawn({
cmd: 'bash -c "ip a | grep \'inet \' | grep -v \'127.0.0.1\' | awk \'{print $2}\' | cut -d/ -f1"',
logger: undefined
});
return output.trim();
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
}
async getPublicIP(): Promise<string> {
try {
const res = await http.request({
url:"https://ipinfo.io/ip",
method:"GET",
})
return res
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
}
async getDNSservers(): Promise<string[]> {
try {
const output = await utils.sp.spawn({
cmd: 'cat /etc/resolv.conf | grep nameserver | awk \'{print $2}\'',
logger: undefined
});
return output.trim().split('\n');
} catch (error) {
return [error instanceof Error ? error.message : String(error)];
}
}
/**
* IPIPDNS
* @returns
*/
async serverInfo(): Promise<any> {
const res = {
localIP: '',
publicIP: '',
dnsServers: [],
}
res.localIP = await this.getLocalIP();
res.publicIP = await this.getPublicIP();
res.dnsServers = await this.getDNSservers();
return res
}
}

View File

@ -8,9 +8,6 @@ importers:
.:
dependencies:
'@certd/ui-server':
specifier: link:packages/ui/certd-server
version: link:packages/ui/certd-server
axios:
specifier: ^1.7.7
version: 1.9.0(debug@4.4.1)
@ -49,7 +46,7 @@ importers:
packages/core/acme-client:
dependencies:
'@certd/basic':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../basic
'@peculiar/x509':
specifier: ^1.11.0
@ -210,10 +207,10 @@ importers:
packages/core/pipeline:
dependencies:
'@certd/basic':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../basic
'@certd/plus-core':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../pro/plus-core
dayjs:
specifier: ^1.11.7
@ -418,7 +415,7 @@ importers:
packages/libs/lib-k8s:
dependencies:
'@certd/basic':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/basic
'@kubernetes/client-node':
specifier: 0.21.0
@ -458,19 +455,19 @@ importers:
packages/libs/lib-server:
dependencies:
'@certd/acme-client':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/pipeline
'@certd/plugin-lib':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../plugins/plugin-lib
'@certd/plus-core':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../pro/plus-core
'@midwayjs/cache':
specifier: 3.14.0
@ -616,16 +613,16 @@ importers:
packages/plugins/plugin-cert:
dependencies:
'@certd/acme-client':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/pipeline
'@certd/plugin-lib':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../plugin-lib
'@google-cloud/publicca':
specifier: ^1.3.0
@ -707,10 +704,10 @@ importers:
specifier: ^3.787.0
version: 3.810.0(aws-crt@1.26.2)
'@certd/basic':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/pipeline
'@kubernetes/client-node':
specifier: 0.21.0
@ -798,19 +795,19 @@ importers:
packages/pro/commercial-core:
dependencies:
'@certd/basic':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/basic
'@certd/lib-server':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../libs/lib-server
'@certd/pipeline':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/pipeline
'@certd/plugin-plus':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../plugin-plus
'@certd/plus-core':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../plus-core
'@midwayjs/core':
specifier: 3.20.11
@ -895,19 +892,19 @@ importers:
specifier: ^1.0.2
version: 1.0.3
'@certd/basic':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/basic
'@certd/lib-k8s':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../libs/lib-k8s
'@certd/pipeline':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/pipeline
'@certd/plugin-cert':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../plugins/plugin-cert
'@certd/plus-core':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../plus-core
ali-oss:
specifier: ^6.21.0
@ -1010,7 +1007,7 @@ importers:
packages/pro/plus-core:
dependencies:
'@certd/basic':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/basic
dayjs:
specifier: ^1.11.7
@ -1306,10 +1303,10 @@ importers:
version: 0.1.3(zod@3.24.4)
devDependencies:
'@certd/lib-iframe':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../libs/lib-iframe
'@certd/pipeline':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/pipeline
'@rollup/plugin-commonjs':
specifier: ^25.0.7
@ -1492,46 +1489,46 @@ importers:
specifier: ^3.705.0
version: 3.810.0(aws-crt@1.26.2)
'@certd/acme-client':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/basic
'@certd/commercial-core':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../pro/commercial-core
'@certd/cv4pve-api-javascript':
specifier: ^8.4.2
version: 8.4.2
'@certd/jdcloud':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../libs/lib-jdcloud
'@certd/lib-huawei':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../libs/lib-huawei
'@certd/lib-k8s':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../libs/lib-k8s
'@certd/lib-server':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../libs/lib-server
'@certd/midway-flyway-js':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../libs/midway-flyway-js
'@certd/pipeline':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../core/pipeline
'@certd/plugin-cert':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../plugins/plugin-cert
'@certd/plugin-lib':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../plugins/plugin-lib
'@certd/plugin-plus':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../pro/plugin-plus
'@certd/plus-core':
specifier: ^1.36.24
specifier: ^1.37.1
version: link:../../pro/plus-core
'@huaweicloud/huaweicloud-sdk-cdn':
specifier: ^3.1.120