mirror of
https://github.com/certd/certd.git
synced 2026-05-14 03:57:30 +08:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b49ddbfef9 | |||
| b92fd73f53 | |||
| 41b8f51a6a | |||
| aad9045de5 | |||
| fdd5848df4 | |||
| 118c15d046 | |||
| bae4f8e320 | |||
| e0189a566e | |||
| 1cd8d73cdb | |||
| d6e9e5987b | |||
| 8c5aa37745 | |||
| a18a871ac3 | |||
| 90cbff9cf9 | |||
| bae5a04dcc | |||
| 7146570392 | |||
| ae88f85d8e | |||
| a362860137 | |||
| c966896522 | |||
| 5f88da1985 | |||
| 043b80a298 | |||
| ed0da28896 | |||
| 61a0d69d58 | |||
| 3833a9216e | |||
| e59566b5e2 | |||
| e4be0ce464 | |||
| 022dbf0cab | |||
| 1e6b559b89 | |||
| 74bae2005d | |||
| 1731a35d94 | |||
| 9f7d766cb3 | |||
| 6c0d0b00c9 | |||
| a82a38421d | |||
| c4b01da384 | |||
| 3e1473dba5 | |||
| d383706554 | |||
| e0eb0e21f6 | |||
| 7266af1749 | |||
| f93bc09438 | |||
| fe3bb7c1b4 | |||
| 923676c7d5 | |||
| 4755216505 | |||
| 37d03c10f9 | |||
| 490b724808 | |||
| d8f132919d | |||
| b8a64a6b5b | |||
| 25ad1e6f86 | |||
| 6b6f1604e9 | |||
| 63be1c1cbd | |||
| b75c625ddc | |||
| 7083e7aff7 | |||
| 9d2937dd4b | |||
| a7e281e278 | |||
| 72b6597817 | |||
| 91a1b97550 | |||
| 9951ab678f | |||
| 930aa355e8 | |||
| e0143fa540 | |||
| 7c1d92ff4b | |||
| 0a0f1e90e1 | |||
| 80092823db | |||
| 146098d9ce | |||
| 519743dbdb | |||
| 7ab661ecd7 | |||
| bb46cb08f7 | |||
| 028932c04a | |||
| 73e6480853 | |||
| aa176b081a | |||
| 267243e71b | |||
| 33fbef8380 | |||
| 45a128a050 | |||
| 2ddc668954 | |||
| 898bc9b9f2 | |||
| d8e5928523 | |||
| 36808a953e | |||
| 39d3f79026 | |||
| 6463e1ca22 | |||
| c985a13544 | |||
| ad76c5177c | |||
| 64b3184b28 | |||
| 2f1ad7201f | |||
| cd23ee2055 | |||
| e00830bebc | |||
| 00e6d580c2 | |||
| 9c7b419e8f | |||
| 95edc0d303 | |||
| 5991b1e37c | |||
| 1aa50cf53a | |||
| eab66e2d19 | |||
| 5b504f094f | |||
| 1460cb9ac1 | |||
| 53782cbf49 | |||
| 0ea22dddf0 |
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
../.trae/skills
|
||||||
Vendored
+10
@@ -65,6 +65,16 @@
|
|||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"internalConsoleOptions": "neverOpen"
|
"internalConsoleOptions": "neverOpen"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "server-new",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/packages/ui/certd-server",
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"runtimeArgs": ["dev-new"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "server-local-plus",
|
"name": "server-local-plus",
|
||||||
"type": "node",
|
"type": "node",
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
# Certd 开发 Agent 上下文
|
||||||
|
|
||||||
|
这个文件是给在本仓库工作的开发 agent 看的常驻项目说明。后续会话进入仓库后,应先读取它,再按任务需要查看具体代码,避免每次都重新全量扫描项目。
|
||||||
|
|
||||||
|
## 项目用途
|
||||||
|
|
||||||
|
Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。它提供 Web 管理台和后端服务,用于证书申请、续期、部署、监控、通知和开放 API 集成。
|
||||||
|
|
||||||
|
它不只是一个简单的 ACME 客户端。项目的核心产品模型是“证书流水线”:
|
||||||
|
|
||||||
|
- 通过 ACME 申请证书
|
||||||
|
- 支持 DNS-01、HTTP-01、CNAME 代理以及各类服务商集成来完成域名验证
|
||||||
|
- 支持将证书转换或导出为 pem、pfx、der、jks、p7b 等格式
|
||||||
|
- 支持把证书部署到主机、Nginx、Kubernetes、CDN、云厂商、面板等目标
|
||||||
|
- 支持通知用户,并监控站点证书过期时间
|
||||||
|
|
||||||
|
由于系统会保存证书、云厂商凭据、SSH 信息、API Key 等敏感数据,产品定位上强烈建议私有化/本地部署。
|
||||||
|
|
||||||
|
## 仓库结构
|
||||||
|
|
||||||
|
这是一个 pnpm + lerna 的 monorepo。
|
||||||
|
|
||||||
|
- `package.json`:根脚本和 workspace 元信息
|
||||||
|
- `pnpm-workspace.yaml`:workspace 包匹配规则
|
||||||
|
- `lerna.json`:lerna-lite 配置
|
||||||
|
- `docs/`:VitePress 文档站
|
||||||
|
- `docker/`:Docker 安装和运行相关文件
|
||||||
|
- `packages/core/acme-client/`:ACME 协议客户端,风格接近 node-acme-client
|
||||||
|
- `packages/core/basic/`:共享基础工具和基础设施
|
||||||
|
- `packages/core/pipeline/`:流水线核心、注册表、装饰器、插件模型、上下文、服务、通知等
|
||||||
|
- `packages/libs/`:共享集成与辅助库,例如 server、Huawei、JDCloud、Kubernetes、iframe
|
||||||
|
- `packages/plugins/plugin-lib/`:通用插件辅助能力和证书相关共享代码
|
||||||
|
- `packages/plugins/plugin-cert/`:证书流水线插件包
|
||||||
|
- `packages/pro/`:商业版/专业版相关包
|
||||||
|
- `packages/ui/certd-server/`:后端服务
|
||||||
|
- `packages/ui/certd-client/`:前端 Web 管理台
|
||||||
|
|
||||||
|
## 后端
|
||||||
|
|
||||||
|
主要后端包:`packages/ui/certd-server`。
|
||||||
|
|
||||||
|
技术栈:
|
||||||
|
|
||||||
|
- Node.js、ESM、TypeScript
|
||||||
|
- MidwayJS 3
|
||||||
|
- Koa
|
||||||
|
- TypeORM
|
||||||
|
- 默认使用 better-sqlite3,同时支持 PostgreSQL 和 MySQL
|
||||||
|
- 通过 `@certd/midway-flyway-js` 使用类似 Flyway 的 SQL 迁移机制
|
||||||
|
|
||||||
|
重要位置:
|
||||||
|
|
||||||
|
- `packages/ui/certd-server/src/config/config.default.ts`:默认服务、静态文件、数据库、定时任务、认证、上传、Swagger 配置
|
||||||
|
- `packages/ui/certd-server/src/config/`:环境与配置加载逻辑
|
||||||
|
- `packages/ui/certd-server/src/configuration.ts`:Midway 应用配置、中间件注册、组件导入
|
||||||
|
- `packages/ui/certd-server/src/modules/`:业务模块,例如 pipeline、cert、cron、monitor、login、open API、sys、plugin、cname、notification
|
||||||
|
- `packages/ui/certd-server/src/controller/`:按 API 领域划分的控制器
|
||||||
|
- `packages/ui/certd-server/src/plugins/`:后端内置的具体服务商、部署、通知等插件
|
||||||
|
- `packages/ui/certd-server/db/migration/`:数据库迁移 SQL
|
||||||
|
- `packages/ui/certd-server/data/`:本地运行数据,例如 SQLite 数据库和生成文件
|
||||||
|
- `packages/ui/certd-server/logs/`:运行日志
|
||||||
|
|
||||||
|
已观察到的默认开发配置:
|
||||||
|
|
||||||
|
- HTTP 端口:`7001`
|
||||||
|
- HTTPS 端口:`7002`
|
||||||
|
- 默认 SQLite 数据库:`./data/db.sqlite`
|
||||||
|
- 默认文件根目录:`./data/files`
|
||||||
|
|
||||||
|
常用脚本:
|
||||||
|
|
||||||
|
- 根目录 `pnpm run start:server`:以生产模式启动后端包
|
||||||
|
- 后端 `pnpm run dev`:启动 Midway watch/dev 服务
|
||||||
|
- 后端 `pnpm run test`:运行后端 mocha 测试
|
||||||
|
- 后端 `pnpm run build`:构建后端并导出插件元数据
|
||||||
|
|
||||||
|
## 前端
|
||||||
|
|
||||||
|
主要前端包:`packages/ui/certd-client`。
|
||||||
|
|
||||||
|
技术栈:
|
||||||
|
|
||||||
|
- Vue 3
|
||||||
|
- Vite
|
||||||
|
- TypeScript
|
||||||
|
- Ant Design Vue
|
||||||
|
- Fast Crud
|
||||||
|
- Pinia
|
||||||
|
- vue-router
|
||||||
|
- vue-i18n
|
||||||
|
- Tailwind/Windi 相关样式工具
|
||||||
|
|
||||||
|
重要位置:
|
||||||
|
|
||||||
|
- `packages/ui/certd-client/src/main.ts`:前端启动入口
|
||||||
|
- `packages/ui/certd-client/src/App.vue`:根组件
|
||||||
|
- `packages/ui/certd-client/src/api/`:API 调用封装
|
||||||
|
- `packages/ui/certd-client/src/router/`:路由
|
||||||
|
- `packages/ui/certd-client/src/store/`:Pinia store
|
||||||
|
- `packages/ui/certd-client/src/views/certd/`:核心产品页面,例如流水线、证书、授权、监控、通知、开放 API、项目、支付、插件
|
||||||
|
- `packages/ui/certd-client/src/components/`:共享 UI 组件
|
||||||
|
- `packages/ui/certd-client/src/locales/`:国际化
|
||||||
|
|
||||||
|
常用脚本:
|
||||||
|
|
||||||
|
- 前端 `pnpm dev`:启动 Vite 开发服务
|
||||||
|
- 前端 `pnpm build`:生产构建
|
||||||
|
- 前端 `pnpm tsc`:类型检查
|
||||||
|
- 前端暂不跑单元测试;当前 `test:unit` 只是占位脚本
|
||||||
|
|
||||||
|
## 流水线与插件模型
|
||||||
|
|
||||||
|
项目最关键的架构概念是证书流水线。
|
||||||
|
|
||||||
|
可以从 `packages/core/pipeline/src/index.ts` 入手,它导出:
|
||||||
|
|
||||||
|
- `core`
|
||||||
|
- `dt`
|
||||||
|
- `access`
|
||||||
|
- `registry`
|
||||||
|
- `plugin`
|
||||||
|
- `context`
|
||||||
|
- `decorator`
|
||||||
|
- `service`
|
||||||
|
- `notification`
|
||||||
|
|
||||||
|
插件是核心能力,不是边缘功能。新增服务商、DNS 验证、证书部署、通知方式等能力,通常应该放在插件包里,或放在 `packages/ui/certd-server/src/plugins/<plugin-name>/` 下。
|
||||||
|
|
||||||
|
后端已看到的插件类型包括:
|
||||||
|
|
||||||
|
- DNS 和注册商服务商:Aliyun、Tencent、Cloudflare、Huawei、JDCloud、AWS、Azure、Google、GoDaddy、Namesilo、Xinnet、West、UCloud、Qiniu、Upyun、Volcengine 等
|
||||||
|
- 部署目标:host、Kubernetes、Nginx Proxy Manager、APISIX、Proxmox、QNAP、Dokploy、GoEdge、各类 CDN、各类面板
|
||||||
|
- 系统/产品插件:notification、captcha、oauth、admin、plus/pro、demo/template
|
||||||
|
|
||||||
|
当修改证书申请、验证、部署或通知行为时,先判断改动属于哪里:
|
||||||
|
|
||||||
|
- ACME client 代码
|
||||||
|
- pipeline 核心抽象
|
||||||
|
- 后端 module/service/entity/controller
|
||||||
|
- 某个具体插件实现
|
||||||
|
- 前端 view/form/schema
|
||||||
|
|
||||||
|
如果只是某个服务商或部署目标的问题,不要轻易修改共享 pipeline/core 行为,除非确实是可复用的公共能力。
|
||||||
|
|
||||||
|
### ACME / EAB 注意事项
|
||||||
|
|
||||||
|
- 公共 EAB(尤其是 Google EAB)可能只能创建一次 ACME 账号。要跨用户复用公共 EAB,应保存并复用同一个 ACME account private key;`accountUrl` 如果存到 `userContext` 里,只能视为当前用户缓存,因为 `userContext` 跟用户 id 走。
|
||||||
|
- ACME 协议的 `newAccount` 支持 `onlyReturnExisting`。使用同一个 account private key 调用 `newAccount({ onlyReturnExisting: true })` 可以取回已创建账号的 URL,且不会再次消费 EAB。
|
||||||
|
- 修改 EAB 的 `kid` 后,应重新生成绑定该 `kid` 的 account private key;否则应阻止继续申请并提示用户刷新账号私钥。
|
||||||
|
|
||||||
|
## 数据与迁移
|
||||||
|
|
||||||
|
后端使用 TypeORM 实体加 SQL 迁移。
|
||||||
|
|
||||||
|
重点查看:
|
||||||
|
|
||||||
|
- `packages/ui/certd-server/src/modules/**/entity/*.ts`
|
||||||
|
- `packages/ui/certd-server/db/migration/*.sql`
|
||||||
|
|
||||||
|
默认配置中 `synchronize: false`,所以涉及表结构变更时,通常应该添加或更新迁移脚本,而不是依赖 TypeORM 自动同步。
|
||||||
|
|
||||||
|
## 开发注意事项
|
||||||
|
|
||||||
|
- 中文 README 在部分 PowerShell 环境中可能显示乱码;`README_en.md` 可读性更好,且包含同样的高层项目说明。
|
||||||
|
- 初次整理时观察到当前分支为 `v2-dev`。
|
||||||
|
- 根包管理器是 pnpm,不要引入 npm/yarn lockfile。
|
||||||
|
- 优先沿用现有模块、插件、服务模式,再考虑新增抽象。
|
||||||
|
- `packages/ui/certd-server/data/`、`logs/`、生成的 metadata/dist 等通常视为运行时或构建产物,除非任务明确要求处理它们。
|
||||||
|
- 注意本地数据和配置里可能包含凭据、证书材料等敏感信息。
|
||||||
|
- 本仓库代码注释优先使用中文,尤其是解释业务规则、兼容逻辑、协议细节和隐藏风险时;除非文件已有明确英文注释风格或引用外部英文术语,否则不要新增英文说明性注释。
|
||||||
|
|
||||||
|
## 插件开发技能
|
||||||
|
|
||||||
|
仓库内置了 Certd 插件开发技能,供 Trae 和 Codex 共用:
|
||||||
|
|
||||||
|
- Trae 入口:`.trae/skills`
|
||||||
|
- Codex 入口:`.codex/skills`
|
||||||
|
|
||||||
|
其中 `.codex/skills` 是指向 `.trae/skills` 的目录链接,不要复制出第二份技能内容。更新技能时只维护 `.trae/skills` 下的原始文件,Codex 会通过 `.codex/skills` 读取同一份内容。
|
||||||
|
|
||||||
|
当前技能包括:
|
||||||
|
|
||||||
|
- `access-plugin-dev`:开发 Access 授权插件
|
||||||
|
- `dns-provider-dev`:开发 DNS Provider 插件
|
||||||
|
- `task-plugin-dev`:开发 Task 部署任务插件
|
||||||
|
- `plugin-converter`:将插件转换为 YAML 配置
|
||||||
|
|
||||||
|
做插件相关任务时,先读取对应技能目录下的 `SKILL.md`,再进入具体实现。若用户在插件开发中指出更好的做法,应总结并更新对应技能。
|
||||||
|
|
||||||
|
## 快速定向命令
|
||||||
|
|
||||||
|
进入项目后,优先使用这些有目标的读取命令,而不是立刻全仓库扫描:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-Content -Encoding utf8 package.json
|
||||||
|
Get-Content -Encoding utf8 pnpm-workspace.yaml
|
||||||
|
Get-Content -Encoding utf8 lerna.json
|
||||||
|
Get-Content -Encoding utf8 README_en.md -TotalCount 180
|
||||||
|
Get-Content -Encoding utf8 packages\ui\certd-server\package.json
|
||||||
|
Get-Content -Encoding utf8 packages\ui\certd-client\package.json
|
||||||
|
Get-ChildItem packages\ui\certd-server\src\modules
|
||||||
|
Get-ChildItem packages\ui\certd-server\src\plugins
|
||||||
|
Get-ChildItem packages\ui\certd-client\src\views\certd
|
||||||
|
```
|
||||||
|
|
||||||
|
## 本仓库 Agent 工作方式
|
||||||
|
|
||||||
|
- 先读本文件,再按用户任务查看相关 package/module。
|
||||||
|
- 在 PowerShell 中读取中文、Markdown、locale、文档类文件时,显式使用 `Get-Content -Encoding utf8`;如果仍然显示乱码,再先执行 `[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()` 后重试。
|
||||||
|
- 做后端任务时,先定位 `packages/ui/certd-server/src/modules` 下的模块,以及相关 entity/service/controller。
|
||||||
|
- 做前端任务时,先定位 `packages/ui/certd-client/src/views/certd` 下的页面,再找对应 `src/api`。
|
||||||
|
- 做服务商、DNS、部署、通知相关任务时,先看 `packages/ui/certd-server/src/plugins`,再看 `packages/plugins/plugin-lib` 里的共享辅助能力。
|
||||||
|
- 做数据库结构变更时,添加或更新迁移脚本,不要依赖 TypeORM 自动同步。
|
||||||
|
- 实现新功能或修复行为缺陷前,先补对应单元测试,并先运行测试确认它处于失败状态;再实现功能或修复代码,反复运行聚焦单元测试直到通过。若某项改动确实不适合先写单元测试,应在回复中说明原因和替代验证方式。
|
||||||
|
- 后补单元测试时,应先基于对正确行为的实际预期编写测试,而不是为了迎合现有实现改写预期;如果运行后出现红灯,且通过测试需要修改已有实现,应先向用户确认这是确实的 bug,还是原本需求/既有行为就是如此;确认后再修改原始实现,避免把测试补充变成未经确认的行为改动。
|
||||||
|
- 后端纯单元测试用例放在 `src` 目录内,并尽量与被测文件相邻,例如 `src/utils/random.test.ts`;对应 `test:unit` 只跑 `src/**/*.test.ts`,构建/打包配置应排除这些 `*.test.ts` 文件。
|
||||||
|
- 单元测试需要 mock ESM 静态 import 时,优先使用 `esmock`,不要为了测试把业务代码改成构造函数注入或把逻辑挪到调用方;各包 `test:unit` 脚本应显式设置 `NODE_ENV=unittest`。
|
||||||
|
- 单个 monorepo 包运行单元测试时,优先使用 `corepack pnpm --dir <包目录> test:unit`,例如 `corepack pnpm --dir packages\ui\certd-server test:unit`、`corepack pnpm --dir packages\core\basic test:unit`、`corepack pnpm --dir packages\plugins\plugin-lib test:unit`;也可以用包名过滤,例如 `corepack pnpm --filter @certd/ui-server test:unit`。前端 `packages\ui\certd-client` 暂时不跑单元测试。
|
||||||
|
- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`,ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
|
||||||
|
- 优先对改动包运行聚焦的测试或类型检查;只有跨包影响明显时再考虑全 monorepo 构建。
|
||||||
@@ -3,6 +3,61 @@
|
|||||||
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.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录彩虹登录不上的bug ([bae4f8e](https://github.com/certd/certd/commit/bae4f8e3209d9f9869ecbd7c01655383bac2fe21))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 优化申请时报错日志增加对应域名打印 ([d6e9e59](https://github.com/certd/certd/commit/d6e9e5987bd52ea12ee18745615486eadd4c87ff))
|
||||||
|
* icon选择器增加一套logo集 ([fdd5848](https://github.com/certd/certd/commit/fdd5848df4055a6ee07dc5eabaaf6b718672882d))
|
||||||
|
* **monitor/site:** 新增站点监控页面禁用启用、检查状态两个筛选条件 ([118c15d](https://github.com/certd/certd/commit/118c15d04633a6ef06f2d9e7a7849d20f596e02c))
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复阿里云订阅流水线创建对话框无法获取阿里订单列表的bug ([a362860](https://github.com/certd/certd/commit/a362860137bfb7072893c844fe775edc46070ee1))
|
||||||
|
* 修复启动时报密钥备份不存在的问题 ([c966896](https://github.com/certd/certd/commit/c9668965226af6b54e0e576931dcba8b3d188ef3))
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **aliyun-access:** 添加阿里云密钥校验失败的错误处理 ([b75c625](https://github.com/certd/certd/commit/b75c625ddcc0b3110699d8e6175681ef157b25df))
|
||||||
|
* cnameProvider域名支持设置子域名托管 ([7266af1](https://github.com/certd/certd/commit/7266af17491a98338022cfb18cfedfb93ca6ef8f))
|
||||||
|
* **plugin-aliyun:** 过滤非CAS证书并优化日志信息 ([c4b01da](https://github.com/certd/certd/commit/c4b01da384bc40a241a673ea8bc01ca733c04d83))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **设置:** 添加首页启用开关配置 ([25ad1e6](https://github.com/certd/certd/commit/25ad1e6f861e43288cc8bd90d4903628e6faec29))
|
||||||
|
* 新增agents.md ([aa176b0](https://github.com/certd/certd/commit/aa176b081a92837d2d6809d16546a8dfc2e5dd36))
|
||||||
|
* **用户资料:** 新增手机号邮箱绑定功能 ([e0eb0e2](https://github.com/certd/certd/commit/e0eb0e21f6dae24b639c944f9aba2c90496ab1c0))
|
||||||
|
* 域名注册过期时间获取再次优化 ([91a1b97](https://github.com/certd/certd/commit/91a1b9755066bf280e194dabf7c3a9f936e2643f))
|
||||||
|
* **证书流水线:** 添加批量更新证书申请参数功能 ([63be1c1](https://github.com/certd/certd/commit/63be1c1cbd9b09a3b48f26130c296b1cedcca1ac))
|
||||||
|
* 支持火山云vke ([bb46cb0](https://github.com/certd/certd/commit/bb46cb08f71f6ae921543f7e4a6c5f4e0190556e))
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
* **domain:** 添加域名过期时间同步进度显示功能 ([9d2937d](https://github.com/certd/certd/commit/9d2937dd4b14ffab73e9b096edd2aa8539811182))
|
||||||
|
* **plugin-volcengine:** 支持火山引擎VKE部署插件 ([b8a64a6](https://github.com/certd/certd/commit/b8a64a6b5bf3691a47177de42bc49b798e795feb))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 调整手机版首页标题被挤开的bug ([eab66e2](https://github.com/certd/certd/commit/eab66e2d1988635985745f2d1b227b958969ee00))
|
||||||
|
* 修复腾讯云clb部署报缺少sslmode参数的bug ([2f1ad72](https://github.com/certd/certd/commit/2f1ad7201f5ed9e00368a28b9e40907d4b415852))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 524错误时重试3次 ([00e6d58](https://github.com/certd/certd/commit/00e6d580c2f54af70fe96a214aff87c4b96426c2))
|
||||||
|
* 阿里云证书订单支持获取2.0的订单 ([64b3184](https://github.com/certd/certd/commit/64b3184b286fee996002d857b0de588452abdadd))
|
||||||
|
* 优化流水线执行时的状态保存性能 ([e00830b](https://github.com/certd/certd/commit/e00830bebcfe6344499e490bc174de96f9fb22d6))
|
||||||
|
* 增加权威NS检查开关,某些用户服务器禁止向黑名单NS服务器发请求 ([1aa50cf](https://github.com/certd/certd/commit/1aa50cf53a0deab752f35ec973912e41ab8161b6))
|
||||||
|
* 支持页脚自定义 ([c985a13](https://github.com/certd/certd/commit/c985a13544aa31b0eb0783f9a3193a7e8bdc6ed6))
|
||||||
|
|
||||||
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -179,19 +179,23 @@ https://certd.handfree.work/
|
|||||||
|
|
||||||
[50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product)
|
[50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product)
|
||||||
|
|
||||||
专业版特权对比
|
专业版、商业版特权对比
|
||||||
|
|
||||||
| 功能 | 免费版 | 专业版 |
|
|
||||||
|---------|---------------------------------------|--------------------------------|
|
|
||||||
| 免费证书申请 | 免费无限制 | 免费无限制 |
|
|
||||||
| 证书域名数量 | 无限制 | 无限制 |
|
|
||||||
| 证书流水线条数 | 无限制 | 无限制 |
|
|
||||||
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 |
|
|
||||||
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 |
|
|
||||||
| 站点监控 | 限制1条 | 无限制 |
|
|
||||||
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 |
|
|
||||||
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 |
|
|
||||||
|
|
||||||
|
| 功能 | 免费版 | 专业版 | 商业版 |
|
||||||
|
|---------|---------------------------------------|--------------------------------|---------------------------------|
|
||||||
|
| 证书申请 | 无限制 | 无限制 | 无限制 |
|
||||||
|
| 证书域名数量 | 无限制 | 无限制 | 无限制 |
|
||||||
|
| 证书流水线条数 | 无限制 | 无限制 | 无限制 |
|
||||||
|
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 | 同专业版 |
|
||||||
|
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 | 同专业版 |
|
||||||
|
| 站点监控 | 限制1条 | 无限制 | 无限制 |
|
||||||
|
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 | 同专业版 |
|
||||||
|
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 | 商业版技术支持 |
|
||||||
|
| 站点个性化 | 无 | 无 | 可自定义站点名称、Logo等,移除Certd元素,首页警告等 |
|
||||||
|
| 套餐功能 | 无 | 无 | 支持配置套餐供用户购买 |
|
||||||
|
| 数据统计 | 无 | 无 | 支持站点各类统计数据 |
|
||||||
|
| 插件管理 | 无 | 无 | 支持公共EAB设置,插件选项配置 |
|
||||||
|
| 是否可商用 | 不允许 | 不允许 | 可对外运营 |
|
||||||
|
|
||||||
## 九、贡献代码
|
## 九、贡献代码
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,74 @@
|
|||||||
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.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复阿里云订阅流水线创建对话框无法获取阿里订单列表的bug ([a362860](https://github.com/certd/certd/commit/a362860137bfb7072893c844fe775edc46070ee1))
|
||||||
|
* 修复启动时报密钥备份不存在的问题 ([c966896](https://github.com/certd/certd/commit/c9668965226af6b54e0e576931dcba8b3d188ef3))
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **aliyun-access:** 添加阿里云密钥校验失败的错误处理 ([b75c625](https://github.com/certd/certd/commit/b75c625ddcc0b3110699d8e6175681ef157b25df))
|
||||||
|
* cnameProvider域名支持设置子域名托管 ([7266af1](https://github.com/certd/certd/commit/7266af17491a98338022cfb18cfedfb93ca6ef8f))
|
||||||
|
* **plugin-aliyun:** 过滤非CAS证书并优化日志信息 ([c4b01da](https://github.com/certd/certd/commit/c4b01da384bc40a241a673ea8bc01ca733c04d83))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **设置:** 添加首页启用开关配置 ([25ad1e6](https://github.com/certd/certd/commit/25ad1e6f861e43288cc8bd90d4903628e6faec29))
|
||||||
|
* 新增agents.md ([aa176b0](https://github.com/certd/certd/commit/aa176b081a92837d2d6809d16546a8dfc2e5dd36))
|
||||||
|
* **用户资料:** 新增手机号邮箱绑定功能 ([e0eb0e2](https://github.com/certd/certd/commit/e0eb0e21f6dae24b639c944f9aba2c90496ab1c0))
|
||||||
|
* 域名注册过期时间获取再次优化 ([91a1b97](https://github.com/certd/certd/commit/91a1b9755066bf280e194dabf7c3a9f936e2643f))
|
||||||
|
* **证书流水线:** 添加批量更新证书申请参数功能 ([63be1c1](https://github.com/certd/certd/commit/63be1c1cbd9b09a3b48f26130c296b1cedcca1ac))
|
||||||
|
* 支持火山云vke ([bb46cb0](https://github.com/certd/certd/commit/bb46cb08f71f6ae921543f7e4a6c5f4e0190556e))
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
* **domain:** 添加域名过期时间同步进度显示功能 ([9d2937d](https://github.com/certd/certd/commit/9d2937dd4b14ffab73e9b096edd2aa8539811182))
|
||||||
|
* **plugin-volcengine:** 支持火山引擎VKE部署插件 ([b8a64a6](https://github.com/certd/certd/commit/b8a64a6b5bf3691a47177de42bc49b798e795feb))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 调整手机版首页标题被挤开的bug ([eab66e2](https://github.com/certd/certd/commit/eab66e2d1988635985745f2d1b227b958969ee00))
|
||||||
|
* 修复腾讯云clb部署报缺少sslmode参数的bug ([2f1ad72](https://github.com/certd/certd/commit/2f1ad7201f5ed9e00368a28b9e40907d4b415852))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 524错误时重试3次 ([00e6d58](https://github.com/certd/certd/commit/00e6d580c2f54af70fe96a214aff87c4b96426c2))
|
||||||
|
* 阿里云证书订单支持获取2.0的订单 ([64b3184](https://github.com/certd/certd/commit/64b3184b286fee996002d857b0de588452abdadd))
|
||||||
|
* 优化流水线执行时的状态保存性能 ([e00830b](https://github.com/certd/certd/commit/e00830bebcfe6344499e490bc174de96f9fb22d6))
|
||||||
|
* 增加权威NS检查开关,某些用户服务器禁止向黑名单NS服务器发请求 ([1aa50cf](https://github.com/certd/certd/commit/1aa50cf53a0deab752f35ec973912e41ab8161b6))
|
||||||
|
* 支持页脚自定义 ([c985a13](https://github.com/certd/certd/commit/c985a13544aa31b0eb0783f9a3193a7e8bdc6ed6))
|
||||||
|
|
||||||
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复列表页面底部滚动条与表格之间有空白间隙的bug ([71cfcad](https://github.com/certd/certd/commit/71cfcad2a15aac0badd85a10c4012a1e713654d1))
|
||||||
|
* 修复流水线未编辑模式下也提示未保存的bug ([64a3503](https://github.com/certd/certd/commit/64a350364d820725b5e69d22ac2416809092f97d))
|
||||||
|
* 修复商业版设置了公共eab,创建流水线仍然会显示需要配置eab的bug ([24dff05](https://github.com/certd/certd/commit/24dff05f6427dadec1e40350214c0167e1d6a73d))
|
||||||
|
* 修复站点监控某些情况下获取不到证书的bug ([a2bbc7e](https://github.com/certd/certd/commit/a2bbc7e27298821d75a36abac6ec05d86dcf51f4))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 支持google dns插件 ([edc7bfc](https://github.com/certd/certd/commit/edc7bfc23043c2c6ef5f3564392f8aac6661c4bf))
|
||||||
|
* 阿里云waf支持云产品接入方式应用的证书部署 ([2f7514a](https://github.com/certd/certd/commit/2f7514a2e7d89a34f833401a983149e667da911b))
|
||||||
|
* 模版创建流水线支持随机时间 ([575415b](https://github.com/certd/certd/commit/575415b93a3e10e1c6e5644f71ddc711ea6f8adc))
|
||||||
|
* 商业版支持配置证书申请插件参数 ([7ac789c](https://github.com/certd/certd/commit/7ac789c9c7e91cdf08dfdae1bb49186552e370e3))
|
||||||
|
* 添加全新的未登录首页和路由配置 ([d1988dc](https://github.com/certd/certd/commit/d1988dc982440472ecf61847ccad76e4c96a80fb))
|
||||||
|
* 添加Azure DNS插件支持及文档 ([1f1d687](https://github.com/certd/certd/commit/1f1d6873172d71fadaa5a0005e1d6f3f528096fc))
|
||||||
|
* 添加HiPMDnsmgr DNS提供商的支持 @WUHINS ([296dcab](https://github.com/certd/certd/commit/296dcab4c7c26cb3f9da1ff748cc6a6b7d83edda))
|
||||||
|
* 为DNS解析器添加超时配置,避免查询时间过长 ([cc5154e](https://github.com/certd/certd/commit/cc5154e04e87f648111119b4eeb4e3cb4dd6cc41))
|
||||||
|
* 优化权威域名服务器查询超时时长 ([77db5ec](https://github.com/certd/certd/commit/77db5ecd12c51293e4de178e43ca0067bc70b46d))
|
||||||
|
* 支持部署到nginx-proxy-manager ([2e6e9ed](https://github.com/certd/certd/commit/2e6e9ed9255bcf178edb0eb00d93a7f13c214430))
|
||||||
|
* 支持一键安装脚本 ([dc969dd](https://github.com/certd/certd/commit/dc969dd7edb6934a29d6657afefe6f8af056741c))
|
||||||
|
* 支持主动修改绑定url地址 ([11b7cfe](https://github.com/certd/certd/commit/11b7cfe5cb7e88e6ebd68d53acb4e5b556550ca9))
|
||||||
|
* apisix支持v2 ([23b4658](https://github.com/certd/certd/commit/23b465867244b199bab9b61863a5ca43644834a9))
|
||||||
|
* **technitium:** 添加Technitium DNS Server插件支持 ([edeb817](https://github.com/certd/certd/commit/edeb817c39597e4fa73a17ff4ca3f712f0320fec))
|
||||||
|
|
||||||
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
|
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
+16
-10
@@ -16,16 +16,22 @@
|
|||||||
****------------------****
|
****------------------****
|
||||||
## 专业版特权对比
|
## 专业版特权对比
|
||||||
|
|
||||||
| 功能 | 免费版 | 专业版 |
|
| 功能 | 免费版 | 专业版 | 商业版 |
|
||||||
|---------|---------------------------------------|--------------------------------|
|
|---------|---------------------------------------|--------------------------------|---------------------------------|
|
||||||
| 证书申请 | 无限制 | 无限制 |
|
| 证书申请 | 无限制 | 无限制 | 无限制 |
|
||||||
| 证书域名数量 | 无限制 | 无限制 |
|
| 证书域名数量 | 无限制 | 无限制 | 无限制 |
|
||||||
| 证书流水线条数 | 无限制 | 无限制 |
|
| 证书流水线条数 | 无限制 | 无限制 | 无限制 |
|
||||||
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 |
|
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 | 同专业版 |
|
||||||
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 |
|
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 | 同专业版 |
|
||||||
| 站点监控 | 限制1条 | 无限制 |
|
| 站点监控 | 限制1条 | 无限制 | 无限制 |
|
||||||
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 |
|
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 | 同专业版 |
|
||||||
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 |
|
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 | 商业版技术支持 |
|
||||||
|
| 站点个性化 | 无 | 无 | 可自定义站点名称、Logo等,移除Certd元素,首页警告等 |
|
||||||
|
| 套餐功能 | 无 | 无 | 支持配置套餐供用户购买 |
|
||||||
|
| 数据统计 | 无 | 无 | 支持站点各类统计数据 |
|
||||||
|
| 插件管理 | 无 | 无 | 支持公共EAB设置,插件选项配置 |
|
||||||
|
| 是否可商用 | 不允许 | 不允许 | 可对外运营 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 专业版激活方式
|
## 专业版激活方式
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|-----|-----|-----|
|
|-----|-----|-----|
|
||||||
| 1.| **证书申请(JS版)** | 免费通配符域名证书申请,支持多个域名打到同一个证书上 |
|
| 1.| **证书申请(JS版)** | 免费通配符域名证书申请,支持多个域名打到同一个证书上 |
|
||||||
| 2.| **已有证书托管** | 手动上传自定义证书后,自动部署(每次证书有更新,都需要手动上传一次) |
|
| 2.| **已有证书托管** | 手动上传自定义证书后,自动部署(每次证书有更新,都需要手动上传一次) |
|
||||||
| 3.| **获取阿里云订阅证书** | 从阿里云拉取订阅模式的商用证书 |
|
| 3.| **获取阿里云订阅证书** | 从阿里云拉取订阅模式的商用证书(支持 API 1.0 和 2.0) |
|
||||||
| 4.| **证书申请(Lego)** | 支持海量DNS解析提供商,推荐使用,一样的免费通配符域名证书申请,支持多个域名打到同一个证书上 |
|
| 4.| **证书申请(Lego)** | 支持海量DNS解析提供商,推荐使用,一样的免费通配符域名证书申请,支持多个域名打到同一个证书上 |
|
||||||
## 2. 主机
|
## 2. 主机
|
||||||
|
|
||||||
@@ -134,8 +134,9 @@
|
|||||||
| 4.| **火山引擎-部署证书至DCDN** | 部署至火山引擎全站加速 |
|
| 4.| **火山引擎-部署证书至DCDN** | 部署至火山引擎全站加速 |
|
||||||
| 5.| **火山引擎-部署证书至Live** | 部署至火山引擎视频直播 |
|
| 5.| **火山引擎-部署证书至Live** | 部署至火山引擎视频直播 |
|
||||||
| 6.| **火山引擎-部署证书至TOS自定义域名** | 仅限TOS自定义域名,加速域名请选择火山引擎的CDN插件 |
|
| 6.| **火山引擎-部署证书至TOS自定义域名** | 仅限TOS自定义域名,加速域名请选择火山引擎的CDN插件 |
|
||||||
| 7.| **火山引擎-部署证书至VOD** | 部署至火山引擎视频点播 |
|
| 7.| **火山引擎-替换VKE证书** | 替换火山引擎VKE集群中的TLS Secret证书 |
|
||||||
| 8.| **火山引擎-上传证书至证书中心** | 上传证书至火山引擎证书中心 |
|
| 8.| **火山引擎-部署证书至VOD** | 部署至火山引擎视频点播 |
|
||||||
|
| 9.| **火山引擎-上传证书至证书中心** | 上传证书至火山引擎证书中心 |
|
||||||
## 9. 京东云
|
## 9. 京东云
|
||||||
|
|
||||||
| 序号 | 名称 | 说明 |
|
| 序号 | 名称 | 说明 |
|
||||||
|
|||||||
+1
-1
@@ -9,5 +9,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"version": "1.39.11"
|
"version": "1.39.15"
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -9,6 +9,7 @@
|
|||||||
"@lerna-lite/run": "^3.9.3",
|
"@lerna-lite/run": "^3.9.3",
|
||||||
"@lerna-lite/version": "^3.9.3",
|
"@lerna-lite/version": "^3.9.3",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"medium-zoom": "^1.1.0",
|
"medium-zoom": "^1.1.0",
|
||||||
"vitepress": "^2.0.0-alpha.4",
|
"vitepress": "^2.0.0-alpha.4",
|
||||||
"vitepress-plugin-lightbox": "^1.0.2"
|
"vitepress-plugin-lightbox": "^1.0.2"
|
||||||
@@ -25,8 +26,8 @@
|
|||||||
"commitAll": "git add . && git commit -m \"build: publish\" && git push && pnpm run commitPro",
|
"commitAll": "git add . && git commit -m \"build: publish\" && git push && pnpm run commitPro",
|
||||||
"commitPro": "cd ./packages/pro/ && git add . && git commit -m \"build: publish\" && git push",
|
"commitPro": "cd ./packages/pro/ && git add . && git commit -m \"build: publish\" && git push",
|
||||||
"copylogs": "copyfiles \"CHANGELOG.md\" ./docs/guide/changelogs/",
|
"copylogs": "copyfiles \"CHANGELOG.md\" ./docs/guide/changelogs/",
|
||||||
"prepublishOnly1": "pnpm run check && lerna run build ",
|
"prepublishOnly1": "pnpm run test:unit && pnpm run check && lerna run build ",
|
||||||
"prepublishOnly2": "pnpm run check && pnpm run before-build && lerna run build && pnpm run plugin-doc-gen",
|
"prepublishOnly2": "pnpm run test:unit && pnpm run check && pnpm run before-build && lerna run build && pnpm run plugin-doc-gen",
|
||||||
"before-build": "pnpm run transform-sql && cd ./packages/core/basic && time /t >build.md && git add ./build.md && git commit -m \"build: prepare to build\"",
|
"before-build": "pnpm run transform-sql && cd ./packages/core/basic && time /t >build.md && git add ./build.md && git commit -m \"build: prepare to build\"",
|
||||||
"deploy1": "node --experimental-json-modules ./scripts/deploy.js ",
|
"deploy1": "node --experimental-json-modules ./scripts/deploy.js ",
|
||||||
"check": "node --experimental-json-modules ./scripts/publish-check.js",
|
"check": "node --experimental-json-modules ./scripts/publish-check.js",
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
"docs:dev": "vitepress dev docs",
|
"docs:dev": "vitepress dev docs",
|
||||||
"docs:build": "pnpm run copylogs && vitepress build docs",
|
"docs:build": "pnpm run copylogs && vitepress build docs",
|
||||||
"docs:preview": "vitepress preview docs",
|
"docs:preview": "vitepress preview docs",
|
||||||
|
"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 ",
|
||||||
"pub_all":"pnpm run -r --parallel pub ",
|
"pub_all":"pnpm run -r --parallel pub ",
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
{
|
{
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2022,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"dist",
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended",
|
||||||
"prettier"
|
"prettier"
|
||||||
@@ -7,9 +16,12 @@
|
|||||||
"eslint-plugin-import"
|
"eslint-plugin-import"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"es2022": true,
|
||||||
"mocha": true
|
"mocha": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"prettier/prettier": "off",
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/ban-ts-ignore": "off",
|
"@typescript-eslint/ban-ts-ignore": "off",
|
||||||
|
|||||||
@@ -4,3 +4,8 @@ node_modules/
|
|||||||
npm-debug.log
|
npm-debug.log
|
||||||
package-lock.json
|
package-lock.json
|
||||||
/.idea/
|
/.idea/
|
||||||
|
/dist/
|
||||||
|
/dist-test/
|
||||||
|
/logs/
|
||||||
|
/tsconfig.tsbuildinfo
|
||||||
|
/tsconfig.test.tsbuildinfo
|
||||||
|
|||||||
@@ -3,6 +3,28 @@
|
|||||||
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.39.15](https://github.com/publishlab/node-acme-client/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 优化申请时报错日志增加对应域名打印 ([d6e9e59](https://github.com/publishlab/node-acme-client/commit/d6e9e5987bd52ea12ee18745615486eadd4c87ff))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/publishlab/node-acme-client/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/publishlab/node-acme-client/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/publishlab/node-acme-client/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/publishlab/node-acme-client/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 增加权威NS检查开关,某些用户服务器禁止向黑名单NS服务器发请求 ([1aa50cf](https://github.com/publishlab/node-acme-client/commit/1aa50cf53a0deab752f35ec973912e41ab8161b6))
|
||||||
|
|
||||||
## [1.39.11](https://github.com/publishlab/node-acme-client/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/publishlab/node-acme-client/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -3,22 +3,22 @@
|
|||||||
"description": "Simple and unopinionated ACME client",
|
"description": "Simple and unopinionated ACME client",
|
||||||
"private": false,
|
"private": false,
|
||||||
"author": "nmorsman",
|
"author": "nmorsman",
|
||||||
"version": "1.39.11",
|
"version": "1.39.15",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "scr/index.js",
|
"module": "./dist/index.js",
|
||||||
"main": "src/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "types/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/publishlab/node-acme-client",
|
"homepage": "https://github.com/publishlab/node-acme-client",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"dist",
|
||||||
"types"
|
"types"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.39.11",
|
"@certd/basic": "^1.39.15",
|
||||||
"@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",
|
||||||
@@ -35,10 +35,12 @@
|
|||||||
"@typescript-eslint/parser": "^8.26.1",
|
"@typescript-eslint/parser": "^8.26.1",
|
||||||
"chai": "^4.4.1",
|
"chai": "^4.4.1",
|
||||||
"chai-as-promised": "^7.1.2",
|
"chai-as-promised": "^7.1.2",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"esmock": "^2.7.5",
|
||||||
"jsdoc-to-markdown": "^8.0.1",
|
"jsdoc-to-markdown": "^8.0.1",
|
||||||
"mocha": "^10.6.0",
|
"mocha": "^10.6.0",
|
||||||
"nock": "^13.5.4",
|
"nock": "^13.5.4",
|
||||||
@@ -47,13 +49,17 @@
|
|||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-docs": "jsdoc2md src/client.js > docs/client.md && jsdoc2md src/crypto/index.js > docs/crypto.md && jsdoc2md src/crypto/forge.js > docs/forge.md",
|
"before-build": "node -e \"const fs=require('fs');fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('tsconfig.tsbuildinfo',{force:true});\"",
|
||||||
"lint": "eslint .",
|
"build": "npm run before-build && tsc --skipLibCheck",
|
||||||
"lint-types": "tsd",
|
"build-docs": "jsdoc2md dist/client.js > docs/client.md && jsdoc2md dist/crypto/index.js > docs/crypto.md && jsdoc2md dist/crypto/forge.js > docs/forge.md",
|
||||||
"prepublishOnly": "npm run build-docs",
|
"lint": "eslint \"src/**/*.ts\" \"types/**/*.ts\"",
|
||||||
|
"lint-types": "tsd --files \"types/index.test-d.ts\"",
|
||||||
|
"prepublishOnly": "npm run build && npm run build-docs",
|
||||||
"test": "mocha -t 60000 \"test/setup.js\" \"test/**/*.spec.js\"",
|
"test": "mocha -t 60000 \"test/setup.js\" \"test/**/*.spec.js\"",
|
||||||
|
"before-test:unit": "node -e \"const fs=require('fs');fs.rmSync('dist-test',{recursive:true,force:true});fs.rmSync('tsconfig.test.tsbuildinfo',{force:true});\"",
|
||||||
|
"test:unit": "cross-env NODE_ENV=unittest npm run before-test:unit && cross-env NODE_ENV=unittest tsc -p tsconfig.test.json --skipLibCheck && cross-env NODE_ENV=unittest mocha -t 60000 \"dist-test/**/*.test.js\"",
|
||||||
"pub": "npm publish",
|
"pub": "npm publish",
|
||||||
"compile": "echo '1'"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -70,5 +76,5 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||||
},
|
},
|
||||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
"gitHead": "bae5a04dcc0a679c290a9805c3ac4a6020eb6ec0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
* ACME API client
|
* ACME API client
|
||||||
*/
|
*/
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
* ACME auto helper
|
* ACME auto helper
|
||||||
*/
|
*/
|
||||||
@@ -166,6 +167,7 @@ export default async (client, userOpts) => {
|
|||||||
await client.completeChallenge(challenge);
|
await client.completeChallenge(challenge);
|
||||||
}catch (e) {
|
}catch (e) {
|
||||||
await deactivateAuth(e);
|
await deactivateAuth(e);
|
||||||
|
e.message = `[${d}] ${e.message || "completeChallenge error"}`;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
challengeCompleted = true;
|
challengeCompleted = true;
|
||||||
@@ -177,6 +179,7 @@ export default async (client, userOpts) => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message || e}`);
|
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message || e}`);
|
||||||
await deactivateAuth(e);
|
await deactivateAuth(e);
|
||||||
|
e.message = `[${d}] ${e.message || "challengeCreateFn error"}`;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
* Axios instance
|
* Axios instance
|
||||||
*/
|
*/
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
* ACME client
|
* ACME client
|
||||||
*
|
*
|
||||||
@@ -494,7 +495,7 @@ class AcmeClient {
|
|||||||
throw new Error('Unable to verify ACME challenge, URL not found');
|
throw new Error('Unable to verify ACME challenge, URL not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const {challenges} = createChallengeFn({logger:this.logger});
|
const {challenges} = createChallengeFn({logger:this.logger,walkFromAuthoritative: this.opts.walkFromAuthoritative});
|
||||||
|
|
||||||
const verify = challenges
|
const verify = challenges
|
||||||
if (typeof verify[challenge.type] === 'undefined') {
|
if (typeof verify[challenge.type] === 'undefined') {
|
||||||
@@ -570,7 +571,7 @@ class AcmeClient {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async waitForValidStatus(item,d) {
|
async waitForValidStatus(item, d?) {
|
||||||
if (!item.url) {
|
if (!item.url) {
|
||||||
throw new Error(`[${d}] Unable to verify status of item, URL not found`);
|
throw new Error(`[${d}] Unable to verify status of item, URL not found`);
|
||||||
}
|
}
|
||||||
+5
-4
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
* Legacy node-forge crypto interface
|
* Legacy node-forge crypto interface
|
||||||
*
|
*
|
||||||
@@ -112,7 +113,7 @@ function parseDomains(obj) {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export async function createPrivateKey(size = 2048) {
|
export async function createPrivateKey(size = 2048): Promise<Buffer> {
|
||||||
const keyPair = await generateKeyPair({ bits: size });
|
const keyPair = await generateKeyPair({ bits: size });
|
||||||
const pemKey = forge.pki.privateKeyToPem(keyPair.privateKey);
|
const pemKey = forge.pki.privateKeyToPem(keyPair.privateKey);
|
||||||
return Buffer.from(pemKey);
|
return Buffer.from(pemKey);
|
||||||
@@ -131,7 +132,7 @@ export async function createPrivateKey(size = 2048) {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const createPublicKey = async (key) => {
|
export const createPublicKey = async (key): Promise<Buffer> => {
|
||||||
const privateKey = forge.pki.privateKeyFromPem(key);
|
const privateKey = forge.pki.privateKeyFromPem(key);
|
||||||
const publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e);
|
const publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e);
|
||||||
const pemKey = forge.pki.publicKeyToPem(publicKey);
|
const pemKey = forge.pki.publicKeyToPem(publicKey);
|
||||||
@@ -174,7 +175,7 @@ export const splitPemChain = (str) => forge.pem.decode(str).map(forge.pem.encode
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const getModulus = async (input) => {
|
export const getModulus = async (input): Promise<Buffer> => {
|
||||||
if (!Buffer.isBuffer(input)) {
|
if (!Buffer.isBuffer(input)) {
|
||||||
input = Buffer.from(input);
|
input = Buffer.from(input);
|
||||||
}
|
}
|
||||||
@@ -197,7 +198,7 @@ export const getModulus = async (input) => {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const getPublicExponent = async (input) => {
|
export const getPublicExponent = async (input): Promise<Buffer> => {
|
||||||
if (!Buffer.isBuffer(input)) {
|
if (!Buffer.isBuffer(input)) {
|
||||||
input = Buffer.from(input);
|
input = Buffer.from(input);
|
||||||
}
|
}
|
||||||
+4
-3
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
* Native Node.js crypto interface
|
* Native Node.js crypto interface
|
||||||
*
|
*
|
||||||
@@ -67,7 +68,7 @@ function getKeyInfo(keyPem) {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export async function createPrivateRsaKey(modulusLength = 2048, encodingType = 'pkcs8') {
|
export async function createPrivateRsaKey(modulusLength = 2048, encodingType = 'pkcs8'): Promise<Buffer> {
|
||||||
const pair = await generateKeyPair('rsa', {
|
const pair = await generateKeyPair('rsa', {
|
||||||
modulusLength,
|
modulusLength,
|
||||||
privateKeyEncoding: {
|
privateKeyEncoding: {
|
||||||
@@ -105,7 +106,7 @@ export const createPrivateKey = createPrivateRsaKey;
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const createPrivateEcdsaKey = async (namedCurve = 'P-256', encodingType = 'pkcs8') => {
|
export const createPrivateEcdsaKey = async (namedCurve = 'P-256', encodingType = 'pkcs8'): Promise<Buffer> => {
|
||||||
const pair = await generateKeyPair('ec', {
|
const pair = await generateKeyPair('ec', {
|
||||||
namedCurve,
|
namedCurve,
|
||||||
privateKeyEncoding: {
|
privateKeyEncoding: {
|
||||||
@@ -129,7 +130,7 @@ export const createPrivateEcdsaKey = async (namedCurve = 'P-256', encodingType =
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const getPublicKey = (keyPem) => {
|
export const getPublicKey = (keyPem): Buffer => {
|
||||||
const info = getKeyInfo(keyPem);
|
const info = getKeyInfo(keyPem);
|
||||||
|
|
||||||
const publicKey = info.publicKey.export({
|
const publicKey = info.publicKey.export({
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
export class CancelError extends Error {
|
export class CancelError extends Error {
|
||||||
constructor(message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
* ACME HTTP client
|
* ACME HTTP client
|
||||||
*/
|
*/
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { directory, getAllSslProviderDomains, getDirectoryUrl } from "./index.js";
|
||||||
|
|
||||||
|
declare const describe: any;
|
||||||
|
declare const it: any;
|
||||||
|
|
||||||
|
describe("directory helpers", () => {
|
||||||
|
it("selects the provider specific directory endpoint", () => {
|
||||||
|
assert.equal(getDirectoryUrl({ sslProvider: "sslcom", pkType: "ec" }), directory.sslcom.ec);
|
||||||
|
assert.equal(getDirectoryUrl({ sslProvider: "letsencrypt", pkType: "rsa" }), directory.letsencrypt.production);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes configured provider domains", () => {
|
||||||
|
const domains = getAllSslProviderDomains();
|
||||||
|
|
||||||
|
assert.ok(domains.includes("acme.litessl.com"));
|
||||||
|
assert.ok(domains.includes("acme.ssl.com"));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
* acme-client
|
* acme-client
|
||||||
*/
|
*/
|
||||||
import AcmeClinet from './client.js'
|
export { default as Client } from './client.js'
|
||||||
export const Client = AcmeClinet
|
export type * from './types.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directory URLs
|
* Directory URLs
|
||||||
@@ -103,4 +104,4 @@ export * from './logger.js'
|
|||||||
export * from './verify.js'
|
export * from './verify.js'
|
||||||
export * from './error.js'
|
export * from './error.js'
|
||||||
|
|
||||||
export * from './util.js'
|
export * from './util.js'
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
* ACME logger
|
* ACME logger
|
||||||
*/
|
*/
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* Account
|
||||||
|
*
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.2
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.3
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.3.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Account {
|
||||||
|
status: "valid" | "deactivated" | "revoked";
|
||||||
|
orders: string;
|
||||||
|
contact?: string[];
|
||||||
|
termsOfServiceAgreed?: boolean;
|
||||||
|
externalAccountBinding?: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccountCreateRequest {
|
||||||
|
contact?: string[];
|
||||||
|
termsOfServiceAgreed?: boolean;
|
||||||
|
onlyReturnExisting?: boolean;
|
||||||
|
externalAccountBinding?: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccountUpdateRequest {
|
||||||
|
status?: string;
|
||||||
|
contact?: string[];
|
||||||
|
termsOfServiceAgreed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order
|
||||||
|
*
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.3
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.4
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Order {
|
||||||
|
status: "pending" | "ready" | "processing" | "valid" | "invalid";
|
||||||
|
identifiers: Identifier[];
|
||||||
|
authorizations: string[];
|
||||||
|
finalize: string;
|
||||||
|
expires?: string;
|
||||||
|
notBefore?: string;
|
||||||
|
notAfter?: string;
|
||||||
|
error?: object;
|
||||||
|
certificate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderCreateRequest {
|
||||||
|
identifiers: Identifier[];
|
||||||
|
notBefore?: string;
|
||||||
|
notAfter?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorization
|
||||||
|
*
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.4
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Authorization {
|
||||||
|
identifier: Identifier;
|
||||||
|
status: "pending" | "valid" | "invalid" | "deactivated" | "expired" | "revoked";
|
||||||
|
challenges: Challenge[];
|
||||||
|
expires?: string;
|
||||||
|
wildcard?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Identifier {
|
||||||
|
type: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Challenge
|
||||||
|
*
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-8
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-8.3
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-8.4
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ChallengeAbstract {
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
status: "pending" | "processing" | "valid" | "invalid";
|
||||||
|
validated?: string;
|
||||||
|
error?: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HttpChallenge extends ChallengeAbstract {
|
||||||
|
type: "http-01";
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DnsChallenge extends ChallengeAbstract {
|
||||||
|
type: "dns-01";
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Challenge = HttpChallenge | DnsChallenge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Certificate
|
||||||
|
*
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.6
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum CertificateRevocationReason {
|
||||||
|
Unspecified = 0,
|
||||||
|
KeyCompromise = 1,
|
||||||
|
CACompromise = 2,
|
||||||
|
AffiliationChanged = 3,
|
||||||
|
Superseded = 4,
|
||||||
|
CessationOfOperation = 5,
|
||||||
|
CertificateHold = 6,
|
||||||
|
RemoveFromCRL = 8,
|
||||||
|
PrivilegeWithdrawn = 9,
|
||||||
|
AACompromise = 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CertificateRevocationRequest {
|
||||||
|
reason?: CertificateRevocationReason;
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import type * as rfc8555 from "./rfc8555.js";
|
||||||
|
import type { Challenge } from "./rfc8555.js";
|
||||||
|
|
||||||
|
export type * from "./rfc8555.js";
|
||||||
|
|
||||||
|
export type PrivateKeyBuffer = Buffer;
|
||||||
|
export type PublicKeyBuffer = Buffer;
|
||||||
|
export type CertificateBuffer = Buffer;
|
||||||
|
export type CsrBuffer = Buffer;
|
||||||
|
|
||||||
|
export type PrivateKeyString = string;
|
||||||
|
export type PublicKeyString = string;
|
||||||
|
export type CertificateString = string;
|
||||||
|
export type CsrString = string;
|
||||||
|
|
||||||
|
export interface Order extends rfc8555.Order {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Authorization extends rfc8555.Authorization {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UrlMapping = {
|
||||||
|
enabled: boolean;
|
||||||
|
mappings: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ClientExternalAccountBindingOptions {
|
||||||
|
kid: string;
|
||||||
|
hmacKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientOptions {
|
||||||
|
sslProvider: string;
|
||||||
|
directoryUrl: string;
|
||||||
|
accountKey: PrivateKeyBuffer | PrivateKeyString;
|
||||||
|
accountUrl?: string;
|
||||||
|
externalAccountBinding?: ClientExternalAccountBindingOptions;
|
||||||
|
backoffAttempts?: number;
|
||||||
|
backoffMin?: number;
|
||||||
|
backoffMax?: number;
|
||||||
|
urlMapping?: UrlMapping;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
logger?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientAutoOptions {
|
||||||
|
csr: CsrBuffer | CsrString;
|
||||||
|
challengeCreateFn: (
|
||||||
|
authz: Authorization,
|
||||||
|
keyAuthorization: (challenge: Challenge) => Promise<string>
|
||||||
|
) => Promise<{ recordReq?: any; recordRes?: any; dnsProvider?: any; challenge: Challenge; keyAuthorization: string }>;
|
||||||
|
challengeRemoveFn: (authz: Authorization, challenge: Challenge, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider: any, httpUploader: any) => Promise<any>;
|
||||||
|
email?: string;
|
||||||
|
termsOfServiceAgreed?: boolean;
|
||||||
|
skipChallengeVerification?: boolean;
|
||||||
|
challengePriority?: string[];
|
||||||
|
preferredChain?: string;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
profile?: string;
|
||||||
|
waitDnsDiffuseTime?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CertificateDomains {
|
||||||
|
commonName: string;
|
||||||
|
altNames: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CertificateIssuer {
|
||||||
|
commonName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CertificateInfo {
|
||||||
|
issuer: CertificateIssuer;
|
||||||
|
domains: CertificateDomains;
|
||||||
|
notAfter: Date;
|
||||||
|
notBefore: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CsrOptions {
|
||||||
|
keySize?: number;
|
||||||
|
commonName?: string;
|
||||||
|
altNames?: string[];
|
||||||
|
country?: string;
|
||||||
|
state?: string;
|
||||||
|
locality?: string;
|
||||||
|
organization?: string;
|
||||||
|
organizationUnit?: string;
|
||||||
|
emailAddress?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RsaPublicJwk {
|
||||||
|
e: string;
|
||||||
|
kty: string;
|
||||||
|
n: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EcdsaPublicJwk {
|
||||||
|
crv: string;
|
||||||
|
kty: string;
|
||||||
|
x: string;
|
||||||
|
y: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CryptoInterface {
|
||||||
|
createPrivateKey(keySize?: number, encodingType?: string): Promise<PrivateKeyBuffer>;
|
||||||
|
createPrivateRsaKey(keySize?: number, encodingType?: string): Promise<PrivateKeyBuffer>;
|
||||||
|
createPrivateEcdsaKey(namedCurve?: "P-256" | "P-384" | "P-521", encodingType?: string): Promise<PrivateKeyBuffer>;
|
||||||
|
getPublicKey(keyPem: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString): PublicKeyBuffer;
|
||||||
|
getJwk(keyPem: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString): RsaPublicJwk | EcdsaPublicJwk;
|
||||||
|
splitPemChain(chainPem: CertificateBuffer | CertificateString): string[];
|
||||||
|
getPemBodyAsB64u(pem: CertificateBuffer | CertificateString): string;
|
||||||
|
readCsrDomains(csrPem: CsrBuffer | CsrString): CertificateDomains;
|
||||||
|
readCertificateInfo(certPem: CertificateBuffer | CertificateString): CertificateInfo;
|
||||||
|
createCsr(data: CsrOptions, keyPem?: PrivateKeyBuffer | PrivateKeyString, encodingType?: string): Promise<[PrivateKeyBuffer, CsrBuffer]>;
|
||||||
|
createAlpnCertificate(authz: Authorization, keyAuthorization: string, keyPem?: PrivateKeyBuffer | PrivateKeyString): Promise<[PrivateKeyBuffer, CertificateBuffer]>;
|
||||||
|
isAlpnCertificateAuthorizationValid(certPem: CertificateBuffer | CertificateString, keyAuthorization: string): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CryptoLegacyInterface {
|
||||||
|
createPrivateKey(size?: number): Promise<PrivateKeyBuffer>;
|
||||||
|
createPublicKey(key: PrivateKeyBuffer | PrivateKeyString): Promise<PublicKeyBuffer>;
|
||||||
|
getPemBody(str: string): string;
|
||||||
|
splitPemChain(str: string): string[];
|
||||||
|
getModulus(input: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString | CertificateBuffer | CertificateString | CsrBuffer | CsrString): Promise<Buffer>;
|
||||||
|
getPublicExponent(input: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString | CertificateBuffer | CertificateString | CsrBuffer | CsrString): Promise<Buffer>;
|
||||||
|
readCsrDomains(csr: CsrBuffer | CsrString): Promise<CertificateDomains>;
|
||||||
|
readCertificateInfo(cert: CertificateBuffer | CertificateString): Promise<CertificateInfo>;
|
||||||
|
createCsr(data: CsrOptions, key?: PrivateKeyBuffer | PrivateKeyString): Promise<[PrivateKeyBuffer, CsrBuffer]>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { formatResponseError, parseRetryAfterHeader, retry } from "./util.js";
|
||||||
|
|
||||||
|
declare const describe: any;
|
||||||
|
declare const it: any;
|
||||||
|
|
||||||
|
describe("util helpers", () => {
|
||||||
|
it("parses retry-after values", () => {
|
||||||
|
assert.equal(parseRetryAfterHeader("120"), 120);
|
||||||
|
assert.equal(parseRetryAfterHeader("invalid"), 0);
|
||||||
|
assert.equal(parseRetryAfterHeader("Wed, 21 Oct 2015 07:28:00 GMT"), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats response errors without newlines", () => {
|
||||||
|
const error = formatResponseError({
|
||||||
|
data: {
|
||||||
|
error: {
|
||||||
|
detail: "line 1\nline 2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(error, "line 1line 2");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("retries until success", async () => {
|
||||||
|
const delays: number[] = [];
|
||||||
|
const originalSetTimeout = globalThis.setTimeout;
|
||||||
|
let attempts = 0;
|
||||||
|
|
||||||
|
(globalThis as any).setTimeout = (fn: (...args: any[]) => void, delay?: number) => {
|
||||||
|
delays.push(Number(delay));
|
||||||
|
return originalSetTimeout(fn, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await retry(
|
||||||
|
async () => {
|
||||||
|
attempts += 1;
|
||||||
|
|
||||||
|
if (attempts < 3) {
|
||||||
|
throw new Error(`boom-${attempts}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "ok";
|
||||||
|
},
|
||||||
|
{ attempts: 3, min: 10, max: 20 },
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(result, "ok");
|
||||||
|
assert.equal(attempts, 3);
|
||||||
|
assert.deepEqual(delays, [10, 20]);
|
||||||
|
} finally {
|
||||||
|
(globalThis as any).setTimeout = originalSetTimeout;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
* Utility methods
|
* Utility methods
|
||||||
*/
|
*/
|
||||||
@@ -252,7 +253,7 @@ async function resolveDomainBySoaRecord(recordName, logger = log) {
|
|||||||
|
|
||||||
async function getAuthoritativeDnsResolver(recordName, logger = log) {
|
async function getAuthoritativeDnsResolver(recordName, logger = log) {
|
||||||
logger(`获取域名${recordName}的权威NS服务器: `);
|
logger(`获取域名${recordName}的权威NS服务器: `);
|
||||||
const resolver = new dns.Resolver({ timeout: 10000,maxTimeout: 60000 });
|
const resolver = new dns.Resolver({timeout: 2000,tries: 2});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/* Resolve root domain by SOA */
|
/* Resolve root domain by SOA */
|
||||||
@@ -352,3 +353,5 @@ export {
|
|||||||
resolveDomainBySoaRecord
|
resolveDomainBySoaRecord
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
/**
|
|
||||||
* ACME challenge verification
|
|
||||||
*/
|
|
||||||
|
|
||||||
import dnsSdk from "dns"
|
|
||||||
import https from 'https'
|
|
||||||
import {log as defaultLog} from './logger.js'
|
|
||||||
import axios from './axios.js'
|
|
||||||
import * as util from './util.js'
|
|
||||||
import {isAlpnCertificateAuthorizationValid} from './crypto/index.js'
|
|
||||||
import {utils} from '@certd/basic'
|
|
||||||
|
|
||||||
const dns = dnsSdk.promises
|
|
||||||
|
|
||||||
|
|
||||||
export function createChallengeFn(opts = {}){
|
|
||||||
const logger = opts?.logger || {info:defaultLog,error:defaultLog,warn:defaultLog,debug:defaultLog}
|
|
||||||
|
|
||||||
const log = function(...args){
|
|
||||||
logger.info(...args)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Verify ACME HTTP challenge
|
|
||||||
*
|
|
||||||
* https://datatracker.ietf.org/doc/html/rfc8555#section-8.3
|
|
||||||
*
|
|
||||||
* @param {object} authz Identifier authorization
|
|
||||||
* @param {object} challenge Authorization challenge
|
|
||||||
* @param {string} keyAuthorization Challenge key authorization
|
|
||||||
* @param {string} [suffix] URL suffix
|
|
||||||
* @returns {Promise<boolean>}
|
|
||||||
*/
|
|
||||||
|
|
||||||
async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix = `/.well-known/acme-challenge/${challenge.token}`) {
|
|
||||||
|
|
||||||
async function doQuery(challengeUrl){
|
|
||||||
log(`正在测试请求 ${challengeUrl} `)
|
|
||||||
// const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
|
|
||||||
// const challengeUrl = `https://${authz.identifier.value}:${httpsPort}${suffix}`;
|
|
||||||
|
|
||||||
/* May redirect to HTTPS with invalid/self-signed cert - https://letsencrypt.org/docs/challenge-types/#http-01-challenge */
|
|
||||||
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
||||||
|
|
||||||
log(`Sending HTTP query to ${authz.identifier.value}, suffix: ${suffix}, port: ${httpPort}`);
|
|
||||||
let data = ""
|
|
||||||
try{
|
|
||||||
const resp = await axios.get(challengeUrl, { httpsAgent });
|
|
||||||
data = (resp.data || '').replace(/\s+$/, '');
|
|
||||||
}catch (e) {
|
|
||||||
log(`[error] HTTP request error from ${authz.identifier.value}`,e.message);
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data || (data !== keyAuthorization)) {
|
|
||||||
log(`[error] Authorization not found in HTTP response from ${authz.identifier.value}`);
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
|
|
||||||
let host = authz.identifier.value;
|
|
||||||
if(utils.domain.isIpv6(host)){
|
|
||||||
host = `[${host}]`;
|
|
||||||
}
|
|
||||||
const challengeUrl = `http://${host}:${httpPort}${suffix}`;
|
|
||||||
|
|
||||||
if (!await doQuery(challengeUrl)) {
|
|
||||||
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
|
|
||||||
const httpsChallengeUrl = `https://${host}:${httpsPort}${suffix}`;
|
|
||||||
const res = await doQuery(httpsChallengeUrl)
|
|
||||||
if (!res) {
|
|
||||||
throw new Error(`[error] 验证失败,请检查以上测试url是否可以正常访问`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
log(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Walk DNS until TXT records are found
|
|
||||||
*/
|
|
||||||
|
|
||||||
async function walkDnsChallengeRecord(recordName, resolver = dns,deep = 0) {
|
|
||||||
|
|
||||||
let records = [];
|
|
||||||
|
|
||||||
/* Resolve TXT records */
|
|
||||||
try {
|
|
||||||
log(`检查域名 ${recordName} 的TXT记录`);
|
|
||||||
const txtRecords = await resolver.resolveTxt(recordName);
|
|
||||||
if (txtRecords && txtRecords.length) {
|
|
||||||
log(`找到 ${txtRecords.length} 条 TXT记录( ${recordName})`);
|
|
||||||
log(`TXT records: ${JSON.stringify(txtRecords)}`);
|
|
||||||
records = records.concat(...txtRecords);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(`解析 TXT 记录出错, ${recordName} :${e.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Resolve CNAME record first */
|
|
||||||
try {
|
|
||||||
log(`检查是否存在CNAME映射: ${recordName}`);
|
|
||||||
const cnameRecords = await resolver.resolveCname(recordName);
|
|
||||||
|
|
||||||
if (cnameRecords.length) {
|
|
||||||
const cnameRecord = cnameRecords[0];
|
|
||||||
log(`已找到${recordName}的CNAME记录,将检查: ${cnameRecord}`);
|
|
||||||
let res= await walkTxtRecord(cnameRecord,deep+1);
|
|
||||||
if (res && res.length) {
|
|
||||||
log(`从CNAME中找到TXT记录: ${JSON.stringify(res)}`);
|
|
||||||
records = records.concat(...res);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
log(`没有CNAME映射(${recordName})`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(`检查CNAME出错(${recordName}) :${e.message}`);
|
|
||||||
}
|
|
||||||
return records
|
|
||||||
}
|
|
||||||
|
|
||||||
async function walkTxtRecord(recordName,deep = 0) {
|
|
||||||
if(deep >5){
|
|
||||||
log(`walkTxtRecord too deep (#${deep}) , skip walk`)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const txtRecords = []
|
|
||||||
try {
|
|
||||||
/* Default DNS resolver first */
|
|
||||||
log('从本地DNS服务器获取TXT解析记录');
|
|
||||||
const res = await walkDnsChallengeRecord(recordName,dns,deep);
|
|
||||||
if (res && res.length > 0) {
|
|
||||||
for (const item of res) {
|
|
||||||
txtRecords.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
log(`本地获取TXT解析记录失败:${e.message}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
/* Authoritative DNS resolver */
|
|
||||||
log(`从域名权威服务器获取TXT解析记录`);
|
|
||||||
const authoritativeResolver = await util.getAuthoritativeDnsResolver(recordName,log);
|
|
||||||
const res = await walkDnsChallengeRecord(recordName, authoritativeResolver,deep);
|
|
||||||
if (res && res.length > 0) {
|
|
||||||
for (const item of res) {
|
|
||||||
txtRecords.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}catch (e) {
|
|
||||||
log(`权威服务器获取TXT解析记录失败:${e.message}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (txtRecords.length === 0) {
|
|
||||||
throw new Error(`没有找到TXT解析记录(${recordName})`);
|
|
||||||
}
|
|
||||||
return txtRecords;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify ACME DNS challenge
|
|
||||||
*
|
|
||||||
* https://datatracker.ietf.org/doc/html/rfc8555#section-8.4
|
|
||||||
*
|
|
||||||
* @param {object} authz Identifier authorization
|
|
||||||
* @param {object} challenge Authorization challenge
|
|
||||||
* @param {string} keyAuthorization Challenge key authorization
|
|
||||||
* @param {string} [prefix] DNS prefix
|
|
||||||
* @returns {Promise<boolean>}
|
|
||||||
*/
|
|
||||||
|
|
||||||
async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '_acme-challenge.') {
|
|
||||||
const recordName = `${prefix}${authz.identifier.value}`;
|
|
||||||
log(`本地校验TXT记录): ${recordName}`);
|
|
||||||
let recordValues = await walkTxtRecord(recordName);
|
|
||||||
//去重
|
|
||||||
recordValues = [...new Set(recordValues)];
|
|
||||||
log(`DNS查询成功, 找到 ${recordValues.length} 条TXT记录:${recordValues}`);
|
|
||||||
if (!recordValues.length || !recordValues.includes(keyAuthorization)) {
|
|
||||||
const err = `没有找到需要的DNS TXT记录: ${recordName},期望:${keyAuthorization},结果:${recordValues}`
|
|
||||||
throw new Error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
log(`关键授权匹配成功(${challenge.type}/${recordName}):${keyAuthorization},校验成功, ACME challenge verified`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify ACME TLS ALPN challenge
|
|
||||||
*
|
|
||||||
* https://datatracker.ietf.org/doc/html/rfc8737
|
|
||||||
*
|
|
||||||
* @param {object} authz Identifier authorization
|
|
||||||
* @param {object} challenge Authorization challenge
|
|
||||||
* @param {string} keyAuthorization Challenge key authorization
|
|
||||||
* @returns {Promise<boolean>}
|
|
||||||
*/
|
|
||||||
|
|
||||||
async function verifyTlsAlpnChallenge(authz, challenge, keyAuthorization) {
|
|
||||||
const tlsAlpnPort = axios.defaults.acmeSettings.tlsAlpnChallengePort || 443;
|
|
||||||
const host = authz.identifier.value;
|
|
||||||
log(`Establishing TLS connection with host: ${host}:${tlsAlpnPort}`);
|
|
||||||
|
|
||||||
const certificate = await util.retrieveTlsAlpnCertificate(host, tlsAlpnPort);
|
|
||||||
log('Certificate received from server successfully, matching key authorization in ALPN');
|
|
||||||
|
|
||||||
if (!isAlpnCertificateAuthorizationValid(certificate, keyAuthorization)) {
|
|
||||||
throw new Error(`Authorization not found in certificate from ${authz.identifier.value}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
log(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
challenges:{
|
|
||||||
'http-01': verifyHttpChallenge,
|
|
||||||
'dns-01': verifyDnsChallenge,
|
|
||||||
'tls-alpn-01': verifyTlsAlpnChallenge,
|
|
||||||
},
|
|
||||||
walkTxtRecord,
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
/**
|
||||||
|
* ACME challenge verification
|
||||||
|
*/
|
||||||
|
|
||||||
|
import dnsSdk from "dns"
|
||||||
|
import https from 'https'
|
||||||
|
import { log as defaultLog } from './logger.js'
|
||||||
|
import axios from './axios.js'
|
||||||
|
import * as util from './util.js'
|
||||||
|
import { isAlpnCertificateAuthorizationValid } from './crypto/index.js'
|
||||||
|
import { utils } from '@certd/basic'
|
||||||
|
|
||||||
|
const dns = dnsSdk.promises
|
||||||
|
|
||||||
|
let walkFromAuthoritative = true
|
||||||
|
export function setWalkFromAuthoritative(value = true) {
|
||||||
|
walkFromAuthoritative = value
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createChallengeFn(opts = {}) {
|
||||||
|
const logger = opts?.logger || { info: defaultLog, error: defaultLog, warn: defaultLog, debug: defaultLog }
|
||||||
|
|
||||||
|
const log = function (...args) {
|
||||||
|
logger.info(...args)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Verify ACME HTTP challenge
|
||||||
|
*
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-8.3
|
||||||
|
*
|
||||||
|
* @param {object} authz Identifier authorization
|
||||||
|
* @param {object} challenge Authorization challenge
|
||||||
|
* @param {string} keyAuthorization Challenge key authorization
|
||||||
|
* @param {string} [suffix] URL suffix
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix = `/.well-known/acme-challenge/${challenge.token}`) {
|
||||||
|
|
||||||
|
async function doQuery(challengeUrl) {
|
||||||
|
log(`正在测试请求 ${challengeUrl} `)
|
||||||
|
// const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
|
||||||
|
// const challengeUrl = `https://${authz.identifier.value}:${httpsPort}${suffix}`;
|
||||||
|
|
||||||
|
/* May redirect to HTTPS with invalid/self-signed cert - https://letsencrypt.org/docs/challenge-types/#http-01-challenge */
|
||||||
|
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
||||||
|
|
||||||
|
log(`Sending HTTP query to ${authz.identifier.value}, suffix: ${suffix}, port: ${httpPort}`);
|
||||||
|
let data = ""
|
||||||
|
try {
|
||||||
|
const resp = await axios.get(challengeUrl, { httpsAgent });
|
||||||
|
data = (resp.data || '').replace(/\s+$/, '');
|
||||||
|
} catch (e) {
|
||||||
|
log(`[error] HTTP request error from ${authz.identifier.value}`, e.message);
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || (data !== keyAuthorization)) {
|
||||||
|
log(`[error] Authorization not found in HTTP response from ${authz.identifier.value}`);
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
|
||||||
|
let host = authz.identifier.value;
|
||||||
|
if (utils.domain.isIpv6(host)) {
|
||||||
|
host = `[${host}]`;
|
||||||
|
}
|
||||||
|
const challengeUrl = `http://${host}:${httpPort}${suffix}`;
|
||||||
|
|
||||||
|
if (!await doQuery(challengeUrl)) {
|
||||||
|
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
|
||||||
|
const httpsChallengeUrl = `https://${host}:${httpsPort}${suffix}`;
|
||||||
|
const res = await doQuery(httpsChallengeUrl)
|
||||||
|
if (!res) {
|
||||||
|
throw new Error(`[error] 验证失败,请检查以上测试url是否可以正常访问`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
log(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walk DNS until TXT records are found
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function walkDnsChallengeRecord(recordName, resolver = dns, deep = 0) {
|
||||||
|
|
||||||
|
let records = [];
|
||||||
|
|
||||||
|
const isAuthoritative = resolver === dns
|
||||||
|
/* Resolve TXT records */
|
||||||
|
try {
|
||||||
|
log(`检查域名 ${recordName} 的TXT记录(from ${isAuthoritative ? '本地DNS' : '权威DNS服务器'})`);
|
||||||
|
const txtRecords = await resolver.resolveTxt(recordName);
|
||||||
|
if (txtRecords && txtRecords.length) {
|
||||||
|
log(`找到 ${txtRecords.length} 条 TXT记录( ${recordName})`);
|
||||||
|
log(`TXT records: ${JSON.stringify(txtRecords)}`);
|
||||||
|
records = records.concat(...txtRecords);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(`解析 TXT 记录出错, ${recordName} :${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolve CNAME record first */
|
||||||
|
try {
|
||||||
|
log(`检查是否存在CNAME映射: ${recordName}`);
|
||||||
|
const cnameRecords = await resolver.resolveCname(recordName);
|
||||||
|
|
||||||
|
if (cnameRecords.length) {
|
||||||
|
const cnameRecord = cnameRecords[0];
|
||||||
|
log(`已找到${recordName}的CNAME记录,将检查: ${cnameRecord}`);
|
||||||
|
let res = await walkTxtRecord(cnameRecord, deep + 1);
|
||||||
|
if (res && res.length) {
|
||||||
|
log(`从CNAME中找到TXT记录: ${JSON.stringify(res)}`);
|
||||||
|
records = records.concat(...res);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log(`没有CNAME映射(${recordName})`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(`检查CNAME出错(${recordName}) :${e.message}`);
|
||||||
|
}
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
async function walkTxtRecord(recordName, deep = 0) {
|
||||||
|
if (deep > 5) {
|
||||||
|
log(`walkTxtRecord too deep (#${deep}) , skip walk`)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const txtRecords = []
|
||||||
|
try {
|
||||||
|
/* Default DNS resolver first */
|
||||||
|
log('从本地DNS服务器获取TXT解析记录');
|
||||||
|
const res = await walkDnsChallengeRecord(recordName, dns, deep);
|
||||||
|
if (res && res.length > 0) {
|
||||||
|
for (const item of res) {
|
||||||
|
txtRecords.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
log(`本地获取TXT解析记录失败:${e.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (walkFromAuthoritative !==false) {
|
||||||
|
try {
|
||||||
|
/* Authoritative DNS resolver */
|
||||||
|
log(`从域名权威服务器获取TXT解析记录`);
|
||||||
|
const authoritativeResolver = await util.getAuthoritativeDnsResolver(recordName, log);
|
||||||
|
const res = await walkDnsChallengeRecord(recordName, authoritativeResolver, deep);
|
||||||
|
if (res && res.length > 0) {
|
||||||
|
for (const item of res) {
|
||||||
|
txtRecords.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(`权威服务器获取TXT解析记录失败:${e.message}`)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
log(`跳过从权威服务器获取TXT解析记录`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (txtRecords.length === 0) {
|
||||||
|
throw new Error(`没有找到TXT解析记录(${recordName})`);
|
||||||
|
}
|
||||||
|
return txtRecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify ACME DNS challenge
|
||||||
|
*
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-8.4
|
||||||
|
*
|
||||||
|
* @param {object} authz Identifier authorization
|
||||||
|
* @param {object} challenge Authorization challenge
|
||||||
|
* @param {string} keyAuthorization Challenge key authorization
|
||||||
|
* @param {string} [prefix] DNS prefix
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '_acme-challenge.') {
|
||||||
|
const recordName = `${prefix}${authz.identifier.value}`;
|
||||||
|
log(`本地校验TXT记录): ${recordName}`);
|
||||||
|
let recordValues = await walkTxtRecord(recordName, 0, walkFromAuthoritative);
|
||||||
|
//去重
|
||||||
|
recordValues = [...new Set(recordValues)];
|
||||||
|
log(`DNS查询成功, 找到 ${recordValues.length} 条TXT记录:${recordValues}`);
|
||||||
|
if (!recordValues.length || !recordValues.includes(keyAuthorization)) {
|
||||||
|
const err = `没有找到需要的DNS TXT记录: ${recordName},期望:${keyAuthorization},结果:${recordValues}`
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`关键授权匹配成功(${challenge.type}/${recordName}):${keyAuthorization},校验成功, ACME challenge verified`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify ACME TLS ALPN challenge
|
||||||
|
*
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8737
|
||||||
|
*
|
||||||
|
* @param {object} authz Identifier authorization
|
||||||
|
* @param {object} challenge Authorization challenge
|
||||||
|
* @param {string} keyAuthorization Challenge key authorization
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function verifyTlsAlpnChallenge(authz, challenge, keyAuthorization) {
|
||||||
|
const tlsAlpnPort = axios.defaults.acmeSettings.tlsAlpnChallengePort || 443;
|
||||||
|
const host = authz.identifier.value;
|
||||||
|
log(`Establishing TLS connection with host: ${host}:${tlsAlpnPort}`);
|
||||||
|
|
||||||
|
const certificate = await util.retrieveTlsAlpnCertificate(host, tlsAlpnPort);
|
||||||
|
log('Certificate received from server successfully, matching key authorization in ALPN');
|
||||||
|
|
||||||
|
if (!isAlpnCertificateAuthorizationValid(certificate, keyAuthorization)) {
|
||||||
|
throw new Error(`Authorization not found in certificate from ${authz.identifier.value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
challenges: {
|
||||||
|
'http-01': verifyHttpChallenge,
|
||||||
|
'dns-01': verifyDnsChallenge,
|
||||||
|
'tls-alpn-01': verifyTlsAlpnChallenge,
|
||||||
|
},
|
||||||
|
walkTxtRecord,
|
||||||
|
walkDnsChallengeRecord,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// createChallengeFn({logger:{info:console.log}}).walkDnsChallengeRecord("handsfree.work")
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
export async function wait(ms) {
|
export async function wait(ms) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(resolve, ms);
|
setTimeout(resolve, ms);
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compileOnSave": true,
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"inlineSourceMap": false,
|
||||||
|
"sourceMap": false,
|
||||||
|
"noImplicitThis": false,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"stripInternal": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"pretty": true,
|
||||||
|
"declaration": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"composite": false,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"strict": false,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": false,
|
||||||
|
"lib": ["ESNext", "DOM"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.json"],
|
||||||
|
"exclude": ["dist", "node_modules", "src/**/*.test.ts", "test"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist-test",
|
||||||
|
"declaration": false,
|
||||||
|
"declarationMap": false,
|
||||||
|
"emitDeclarationOnly": false
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["dist", "dist-test", "node_modules", "test"]
|
||||||
|
}
|
||||||
+15
-4
@@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
import { AxiosInstance } from 'axios';
|
import { AxiosInstance } from 'axios';
|
||||||
import * as rfc8555 from './rfc8555';
|
import * as rfc8555 from './rfc8555';
|
||||||
import {CancelError} from '../src/error.js'
|
|
||||||
export * from '../src/error.js'
|
|
||||||
|
|
||||||
export type PrivateKeyBuffer = Buffer;
|
export type PrivateKeyBuffer = Buffer;
|
||||||
export type PublicKeyBuffer = Buffer;
|
export type PublicKeyBuffer = Buffer;
|
||||||
@@ -115,6 +113,15 @@ export const directory: {
|
|||||||
zerossl: {
|
zerossl: {
|
||||||
staging: string,
|
staging: string,
|
||||||
production: string
|
production: string
|
||||||
|
},
|
||||||
|
sslcom: {
|
||||||
|
staging: string,
|
||||||
|
production: string,
|
||||||
|
ec: string
|
||||||
|
},
|
||||||
|
litessl: {
|
||||||
|
staging: string,
|
||||||
|
production: string
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -211,12 +218,16 @@ export const agents: any;
|
|||||||
* Logger
|
* Logger
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export class CancelError extends Error {
|
||||||
|
constructor(message?: string);
|
||||||
|
}
|
||||||
|
|
||||||
export function setLogger(fn: (message: any, ...args: any[]) => void): void;
|
export function setLogger(fn: (message: any, ...args: any[]) => void): void;
|
||||||
|
|
||||||
export function createChallengeFn(opts?: {logger?:any}): any;
|
export function createChallengeFn(opts?: {logger?:any}): any;
|
||||||
// export function walkTxtRecord(record: any): Promise<string[]>;
|
// export function walkTxtRecord(record: any): Promise<string[]>;
|
||||||
export function getAuthoritativeDnsResolver(record:string): Promise<any>;
|
export function getAuthoritativeDnsResolver(record:string): Promise<any>;
|
||||||
|
|
||||||
export const CancelError: typeof CancelError;
|
export function resolveDomainBySoaRecord(domain: string): Promise<string>;
|
||||||
|
|
||||||
export function resolveDomainBySoaRecord(domain: string): Promise<string>;
|
export function setWalkFromAuthoritative(value?: boolean): void;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* acme-client type definition tests
|
* acme-client type definition tests
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as acme from 'acme-client';
|
import * as acme from '..';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
/* Client */
|
/* Client */
|
||||||
@@ -10,6 +10,7 @@ import * as acme from 'acme-client';
|
|||||||
|
|
||||||
const client = new acme.Client({
|
const client = new acme.Client({
|
||||||
accountKey,
|
accountKey,
|
||||||
|
sslProvider: 'letsencrypt',
|
||||||
directoryUrl: acme.directory.letsencrypt.staging
|
directoryUrl: acme.directory.letsencrypt.staging
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -52,7 +53,10 @@ import * as acme from 'acme-client';
|
|||||||
/* Auto */
|
/* Auto */
|
||||||
await client.auto({
|
await client.auto({
|
||||||
csr: certCsr,
|
csr: certCsr,
|
||||||
challengeCreateFn: async (authz, challenge, keyAuthorization) => {},
|
challengeCreateFn: async (authz, keyAuthorization) => ({
|
||||||
|
challenge: authz.challenges[0],
|
||||||
|
keyAuthorization: await keyAuthorization(authz.challenges[0])
|
||||||
|
}),
|
||||||
challengeRemoveFn: async (authz, challenge, keyAuthorization) => {}
|
challengeRemoveFn: async (authz, challenge, keyAuthorization) => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,7 +67,10 @@ import * as acme from 'acme-client';
|
|||||||
skipChallengeVerification: false,
|
skipChallengeVerification: false,
|
||||||
challengePriority: ['http-01', 'dns-01'],
|
challengePriority: ['http-01', 'dns-01'],
|
||||||
preferredChain: 'DST Root CA X3',
|
preferredChain: 'DST Root CA X3',
|
||||||
challengeCreateFn: async (authz, challenge, keyAuthorization) => {},
|
challengeCreateFn: async (authz, keyAuthorization) => ({
|
||||||
|
challenge: authz.challenges[0],
|
||||||
|
keyAuthorization: await keyAuthorization(authz.challenges[0])
|
||||||
|
}),
|
||||||
challengeRemoveFn: async (authz, challenge, keyAuthorization) => {}
|
challengeRemoveFn: async (authz, challenge, keyAuthorization) => {}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -3,6 +3,28 @@
|
|||||||
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.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 524错误时重试3次 ([00e6d58](https://github.com/certd/certd/commit/00e6d580c2f54af70fe96a214aff87c4b96426c2))
|
||||||
|
|
||||||
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/basic
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
13:28
|
13:33
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/basic",
|
"name": "@certd/basic",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.11",
|
"version": "1.39.15",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
"dev-build": "npm run build",
|
"dev-build": "npm run build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "mocha --loader=ts-node/esm",
|
"test": "mocha --loader=ts-node/esm",
|
||||||
|
"test:unit": "cross-env NODE_ENV=unittest mocha --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||||
"pub": "npm publish",
|
"pub": "npm publish",
|
||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
@@ -39,13 +40,17 @@
|
|||||||
"@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",
|
||||||
"chai": "4.3.10",
|
"chai": "4.3.10",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.41.0",
|
"eslint": "^8.41.0",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"esmock": "^2.7.5",
|
||||||
|
"mocha": "^10.2.0",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
"gitHead": "bae5a04dcc0a679c290a9805c3ac4a6020eb6ec0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { amountUtils } from "./util.amount.js";
|
||||||
|
|
||||||
|
describe("amountUtils", () => {
|
||||||
|
describe("toCent", () => {
|
||||||
|
it("converts yuan values to cents", () => {
|
||||||
|
expect(amountUtils.toCent(1)).to.equal(100);
|
||||||
|
expect(amountUtils.toCent(12.34)).to.equal(1234);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rounds to the nearest cent", () => {
|
||||||
|
expect(amountUtils.toCent(1.235)).to.equal(124);
|
||||||
|
expect(amountUtils.toCent(1.234)).to.equal(123);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("toYuan", () => {
|
||||||
|
it("converts cent values to yuan", () => {
|
||||||
|
expect(amountUtils.toYuan(100)).to.equal(1);
|
||||||
|
expect(amountUtils.toYuan(1234)).to.equal(12.34);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rounds yuan values to two decimal places", () => {
|
||||||
|
expect(amountUtils.toYuan(1235)).to.equal(12.35);
|
||||||
|
expect(amountUtils.toYuan(1)).to.equal(0.01);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { domainUtils } from "./util.domain.js";
|
||||||
|
|
||||||
|
describe("domainUtils", () => {
|
||||||
|
describe("match", () => {
|
||||||
|
it("matches exact domains", () => {
|
||||||
|
expect(domainUtils.match("example.com", ["example.com"])).to.equal(true);
|
||||||
|
expect(domainUtils.match("api.example.com", ["example.com"])).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matches wildcard domains by suffix", () => {
|
||||||
|
expect(domainUtils.match("api.example.com", ["*.example.com"])).to.equal(true);
|
||||||
|
expect(domainUtils.match("deep.api.example.com", ["*.example.com"])).to.equal(false);
|
||||||
|
expect(domainUtils.match("example.com", ["*.example.com"])).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requires every target domain to match", () => {
|
||||||
|
expect(domainUtils.match(["api.example.com", "admin.example.com"], ["*.example.com"])).to.equal(true);
|
||||||
|
expect(domainUtils.match(["api.example.com", "other.com"], ["*.example.com"])).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isIp", () => {
|
||||||
|
it("detects valid IPv4 addresses", () => {
|
||||||
|
expect(domainUtils.isIpv4("127.0.0.1")).to.equal(true);
|
||||||
|
expect(domainUtils.isIpv4("255.255.255.255")).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects invalid IPv4 addresses", () => {
|
||||||
|
expect(domainUtils.isIpv4("999.1.1.1")).to.equal(false);
|
||||||
|
expect(domainUtils.isIpv4("1.2.3")).to.equal(false);
|
||||||
|
expect(domainUtils.isIpv4("example.com")).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects IPv6 addresses", () => {
|
||||||
|
expect(domainUtils.isIpv6("2001:db8::1")).to.equal(true);
|
||||||
|
expect(domainUtils.isIp("2001:db8::1")).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -51,7 +51,10 @@ function isIpv4(d: string) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const isIPv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
const isIPv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||||
return isIPv4Regex.test(d);
|
if (!isIPv4Regex.test(d)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return d.split(".").every(item => Number(item) <= 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isIpv6(d: string) {
|
function isIpv6(d: string) {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { isDev } from "./util.env.js";
|
||||||
|
|
||||||
|
describe("isDev", () => {
|
||||||
|
const originalNodeEnv = process.env.NODE_ENV;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (originalNodeEnv == null) {
|
||||||
|
delete process.env.NODE_ENV;
|
||||||
|
} else {
|
||||||
|
process.env.NODE_ENV = originalNodeEnv;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats missing NODE_ENV as development", () => {
|
||||||
|
delete process.env.NODE_ENV;
|
||||||
|
|
||||||
|
expect(isDev()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects development-like NODE_ENV values", () => {
|
||||||
|
process.env.NODE_ENV = "development";
|
||||||
|
expect(isDev()).to.equal(true);
|
||||||
|
|
||||||
|
process.env.NODE_ENV = "local";
|
||||||
|
expect(isDev()).to.equal(true);
|
||||||
|
|
||||||
|
process.env.NODE_ENV = "dev-test";
|
||||||
|
expect(isDev()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects production-like NODE_ENV values", () => {
|
||||||
|
process.env.NODE_ENV = "production";
|
||||||
|
expect(isDev()).to.equal(false);
|
||||||
|
|
||||||
|
process.env.NODE_ENV = "test";
|
||||||
|
expect(isDev()).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { hashUtils } from "./util.hash.js";
|
||||||
|
|
||||||
|
describe("hashUtils", () => {
|
||||||
|
describe("digest helpers", () => {
|
||||||
|
it("generates md5 and sha256 hex digests by default", () => {
|
||||||
|
expect(hashUtils.md5("certd")).to.equal("3f3d9f715fcc63d54a4a224e0939a233");
|
||||||
|
expect(hashUtils.sha256("certd")).to.equal("26a6366060d2a6477185c05075155769cb438c6c71f61f509535b8516594ad92");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports alternate digest encodings", () => {
|
||||||
|
expect(hashUtils.md5("certd", "base64")).to.equal("Pz2fcV/MY9VKSiJOCTmiMw==");
|
||||||
|
expect(hashUtils.sha256("certd", "base64")).to.equal("JqY2YGDSpkdxhcBQdRVXactDjGxx9h9QlTW4UWWUrZI=");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("hmac helpers", () => {
|
||||||
|
it("signs data with a provided key", () => {
|
||||||
|
expect(hashUtils.hmacSha256WithKey("secret", "certd")).to.equal("kh/kUD/Ji8FHfpt4vYUHZx+1BZvKSyyklZIiuS+Rzlg=");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses an empty payload when only the key is provided", () => {
|
||||||
|
expect(hashUtils.hmacSha256("secret")).to.equal("+eZuF5tnR65UEI+C+K3os8Jddv0wr95sOVgixTAZYWk=");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("encoding helpers", () => {
|
||||||
|
it("round trips base64 values", () => {
|
||||||
|
const encoded = hashUtils.base64("证书-certd");
|
||||||
|
|
||||||
|
expect(encoded).to.equal("6K+B5LmmLWNlcnRk");
|
||||||
|
expect(hashUtils.base64Decode(encoded)).to.equal("证书-certd");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("converts strings and numbers to hex", () => {
|
||||||
|
expect(hashUtils.toHex("certd")).to.equal("6365727464");
|
||||||
|
expect(hashUtils.hexToStr("6365727464")).to.equal("certd");
|
||||||
|
expect(hashUtils.toHex(255)).to.equal("ff");
|
||||||
|
expect(hashUtils.hexToNumber("ff")).to.equal(255);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { randomNumber, simpleNanoId } from "./util.id.js";
|
||||||
|
|
||||||
|
describe("id utils", () => {
|
||||||
|
it("generates a four digit random number string", () => {
|
||||||
|
expect(randomNumber()).to.match(/^\d{4}$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("generates a twelve character simple nano id", () => {
|
||||||
|
const id = simpleNanoId();
|
||||||
|
|
||||||
|
expect(id).to.have.length(12);
|
||||||
|
expect(id).to.match(/^[0-9a-zA-Z]+$/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { mergeUtils, UnMergeable } from "./util.merge.js";
|
||||||
|
|
||||||
|
describe("mergeUtils", () => {
|
||||||
|
describe("merge", () => {
|
||||||
|
it("deep merges plain objects", () => {
|
||||||
|
const target = { acme: { email: "admin@example.com" }, deploy: { retries: 1 } };
|
||||||
|
|
||||||
|
const result = mergeUtils.merge(target, { acme: { dnsProvider: "cloudflare" } });
|
||||||
|
|
||||||
|
expect(result).to.equal(target);
|
||||||
|
expect(result).to.deep.equal({
|
||||||
|
acme: { email: "admin@example.com", dnsProvider: "cloudflare" },
|
||||||
|
deploy: { retries: 1 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaces arrays instead of merging them by index", () => {
|
||||||
|
const result = mergeUtils.merge({ domains: ["old.example.com", "legacy.example.com"] }, { domains: ["new.example.com"] });
|
||||||
|
|
||||||
|
expect(result).to.deep.equal({ domains: ["new.example.com"] });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows null to clear nested values", () => {
|
||||||
|
const result = mergeUtils.merge({ cert: { name: "certd" } }, { cert: null });
|
||||||
|
|
||||||
|
expect(result).to.deep.equal({ cert: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps undefined sources from overwriting existing nested values", () => {
|
||||||
|
const result = mergeUtils.merge({ cert: { name: "certd" } }, { cert: undefined });
|
||||||
|
|
||||||
|
expect(result).to.deep.equal({ cert: { name: "certd" } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an UnMergeable source directly when it is merged at the top level", () => {
|
||||||
|
const source = new UnMergeable();
|
||||||
|
|
||||||
|
const result = mergeUtils.merge({ enabled: true }, source);
|
||||||
|
|
||||||
|
expect(result).to.equal(source);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaces nested values marked as UnMergeable", () => {
|
||||||
|
const source = new UnMergeable();
|
||||||
|
|
||||||
|
const result = mergeUtils.merge({ plugin: { enabled: true } }, { plugin: source });
|
||||||
|
|
||||||
|
expect(result.plugin).to.equal(source);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("cloneDeep", () => {
|
||||||
|
it("deep clones plain values", () => {
|
||||||
|
const source = { acme: { email: "admin@example.com" }, domains: ["example.com"] };
|
||||||
|
|
||||||
|
const result = mergeUtils.cloneDeep(source);
|
||||||
|
|
||||||
|
expect(result).to.deep.equal(source);
|
||||||
|
expect(result).not.to.equal(source);
|
||||||
|
expect(result.acme).not.to.equal(source.acme);
|
||||||
|
expect(result.domains).not.to.equal(source.domains);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves references marked as not cloneable", () => {
|
||||||
|
const uncloneable = new UnMergeable();
|
||||||
|
const source = { plugin: uncloneable };
|
||||||
|
|
||||||
|
const result = mergeUtils.cloneDeep(source);
|
||||||
|
|
||||||
|
expect(result).not.to.equal(source);
|
||||||
|
expect(result.plugin).to.equal(uncloneable);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { optionsUtils } from "./util.options.js";
|
||||||
|
|
||||||
|
describe("optionsUtils", () => {
|
||||||
|
describe("groupByDomain", () => {
|
||||||
|
it("splits options by domain match", () => {
|
||||||
|
const matchedOption = { value: "matched", domain: "api.example.com" };
|
||||||
|
const wildcardMatchedOption = { value: "wildcard", domain: "admin.example.com" };
|
||||||
|
const unmatchedOption = { value: "unmatched", domain: "other.com" };
|
||||||
|
|
||||||
|
const result = optionsUtils.groupByDomain([matchedOption, wildcardMatchedOption, unmatchedOption], ["api.example.com", "*.example.com"]);
|
||||||
|
|
||||||
|
expect(result.matched).to.deep.equal([matchedOption, wildcardMatchedOption]);
|
||||||
|
expect(result.notMatched).to.deep.equal([unmatchedOption]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats options without matching domains as not matched", () => {
|
||||||
|
const optionWithoutDomain = { value: "empty" };
|
||||||
|
|
||||||
|
const result = optionsUtils.groupByDomain([optionWithoutDomain], ["example.com"]);
|
||||||
|
|
||||||
|
expect(result.matched).to.deep.equal([]);
|
||||||
|
expect(result.notMatched).to.deep.equal([optionWithoutDomain]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buildGroupOptions", () => {
|
||||||
|
it("builds disabled group labels around matched and unmatched options", () => {
|
||||||
|
const matchedOption = { value: "matched", domain: "api.example.com" };
|
||||||
|
const unmatchedOption = { value: "unmatched", domain: "other.com" };
|
||||||
|
|
||||||
|
const result = optionsUtils.buildGroupOptions([matchedOption, unmatchedOption], ["api.example.com"]);
|
||||||
|
|
||||||
|
expect(result).to.deep.equal([{ value: "matched", disabled: true, label: "----已匹配----" }, matchedOption, { value: "unmatched", disabled: true, label: "----未匹配----" }, unmatchedOption]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { logger } from "./util.log.js";
|
||||||
|
import { promises } from "./util.promise.js";
|
||||||
|
|
||||||
|
describe("promises", () => {
|
||||||
|
describe("TimeoutPromise", () => {
|
||||||
|
it("resolves when the callback finishes before the timeout", async () => {
|
||||||
|
let completed = false;
|
||||||
|
|
||||||
|
await promises.TimeoutPromise(async () => {
|
||||||
|
completed = true;
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
expect(completed).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects when the callback exceeds the timeout", async () => {
|
||||||
|
try {
|
||||||
|
await promises.TimeoutPromise(() => new Promise<void>(resolve => setTimeout(resolve, 30)), 5);
|
||||||
|
expect.fail("expected TimeoutPromise to reject");
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e.message).to.equal("Task timeout in 5 ms");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("safePromise", () => {
|
||||||
|
it("resolves values provided by the callback", async () => {
|
||||||
|
const result = await promises.safePromise<string>(resolve => {
|
||||||
|
resolve("ok");
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).to.equal("ok");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects synchronous errors thrown by the callback", async () => {
|
||||||
|
const oldLevel = logger.level;
|
||||||
|
logger.level = "fatal";
|
||||||
|
try {
|
||||||
|
await promises.safePromise(() => {
|
||||||
|
throw new Error("boom");
|
||||||
|
});
|
||||||
|
expect.fail("expected safePromise to reject");
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e.message).to.equal("boom");
|
||||||
|
} finally {
|
||||||
|
logger.level = oldLevel;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("promisify", () => {
|
||||||
|
it("resolves callback data", async () => {
|
||||||
|
const readValue = promises.promisify((prefix: string, callback: (err: Error | null, data?: string) => void) => {
|
||||||
|
callback(null, `${prefix}-value`);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await readValue("certd")).to.equal("certd-value");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects callback errors", async () => {
|
||||||
|
const failing = promises.promisify((callback: (err: Error) => void) => {
|
||||||
|
callback(new Error("callback failed"));
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await failing();
|
||||||
|
expect.fail("expected promisified function to reject");
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e.message).to.equal("callback failed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects synchronous errors", async () => {
|
||||||
|
const failing = promises.promisify(() => {
|
||||||
|
throw new Error("sync failed");
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await failing();
|
||||||
|
expect.fail("expected promisified function to reject");
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e.message).to.equal("sync failed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { expect } from "chai";
|
||||||
|
import { createAxiosService, HttpClient, setGlobalHeaders } from "./util.request.js";
|
||||||
|
import { ILogger } from "./util.log.js";
|
||||||
|
|
||||||
|
const testLogger = {
|
||||||
|
info() {},
|
||||||
|
error() {},
|
||||||
|
} as unknown as ILogger;
|
||||||
|
|
||||||
|
describe("util.request", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
setGlobalHeaders({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should merge global headers without overriding request headers", async () => {
|
||||||
|
setGlobalHeaders({
|
||||||
|
"X-Common": "common",
|
||||||
|
"X-Override": "global",
|
||||||
|
});
|
||||||
|
|
||||||
|
const http = createAxiosService({ logger: testLogger }) as HttpClient;
|
||||||
|
const res = await http.request({
|
||||||
|
url: "http://example.com",
|
||||||
|
method: "get",
|
||||||
|
logReq: false,
|
||||||
|
logRes: false,
|
||||||
|
headers: {
|
||||||
|
"X-Override": "request",
|
||||||
|
"X-Request": "request",
|
||||||
|
},
|
||||||
|
adapter: async config => {
|
||||||
|
const headers = config.headers;
|
||||||
|
return {
|
||||||
|
config,
|
||||||
|
data: {
|
||||||
|
common: headers.get("X-Common"),
|
||||||
|
override: headers.get("X-Override"),
|
||||||
|
request: headers.get("X-Request"),
|
||||||
|
},
|
||||||
|
headers: {},
|
||||||
|
status: 200,
|
||||||
|
statusText: "OK",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).to.deep.equal({
|
||||||
|
common: "common",
|
||||||
|
override: "request",
|
||||||
|
request: "request",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -26,7 +26,7 @@ export class HttpError extends Error {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = error?.message || error?.response.statusText || error?.code;
|
let message = error?.message || error?.response?.statusText || error?.code;
|
||||||
if (message && typeof message === "string" && message.indexOf) {
|
if (message && typeof message === "string" && message.indexOf) {
|
||||||
for (const key in errorMap) {
|
for (const key in errorMap) {
|
||||||
if (message.indexOf(key) > -1) {
|
if (message.indexOf(key) > -1) {
|
||||||
@@ -82,6 +82,7 @@ export class HttpError extends Error {
|
|||||||
export const HttpCommonError = HttpError;
|
export const HttpCommonError = HttpError;
|
||||||
|
|
||||||
let defaultAgents = createAgent();
|
let defaultAgents = createAgent();
|
||||||
|
let defaultHeaders: Record<string, string> = {};
|
||||||
|
|
||||||
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) {
|
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) {
|
||||||
logger.info("setGlobalProxy:", opts);
|
logger.info("setGlobalProxy:", opts);
|
||||||
@@ -92,6 +93,15 @@ export function getGlobalAgents() {
|
|||||||
return defaultAgents;
|
return defaultAgents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setGlobalHeaders(headers: Record<string, string> = {}) {
|
||||||
|
logger.info("setGlobalHeaders:", Object.keys(headers));
|
||||||
|
defaultHeaders = { ...headers };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGlobalHeaders() {
|
||||||
|
return defaultHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 创建请求实例
|
* @description 创建请求实例
|
||||||
*/
|
*/
|
||||||
@@ -111,8 +121,13 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
|
|||||||
if (config.logData == null) {
|
if (config.logData == null) {
|
||||||
config.logData = false;
|
config.logData = false;
|
||||||
}
|
}
|
||||||
|
if (config.logReq == null) {
|
||||||
|
config.logReq = true;
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(`http request:${config.url},method:${config.method}`);
|
if (config.logReq !== false) {
|
||||||
|
logger.info(`http request:${config.url},method:${config.method}`);
|
||||||
|
}
|
||||||
if (config.logParams !== false && config.params) {
|
if (config.logParams !== false && config.params) {
|
||||||
logger.info(`params:${JSON.stringify(config.params)}`);
|
logger.info(`params:${JSON.stringify(config.params)}`);
|
||||||
}
|
}
|
||||||
@@ -143,6 +158,12 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
|
|||||||
config.httpsAgent = agents.httpsAgent;
|
config.httpsAgent = agents.httpsAgent;
|
||||||
config.httpAgent = agents.httpAgent;
|
config.httpAgent = agents.httpAgent;
|
||||||
|
|
||||||
|
if (Object.keys(defaultHeaders).length > 0) {
|
||||||
|
const headers = AxiosHeaders.from(defaultHeaders);
|
||||||
|
headers.set(config.headers || {});
|
||||||
|
config.headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
// const agent = new https.Agent({
|
// const agent = new https.Agent({
|
||||||
// rejectUnauthorized: false // 允许自签名证书
|
// rejectUnauthorized: false // 允许自签名证书
|
||||||
// });
|
// });
|
||||||
@@ -151,10 +172,11 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
|
|||||||
|
|
||||||
config.retry = merge(
|
config.retry = merge(
|
||||||
{
|
{
|
||||||
status: [421],
|
status: [421, 524],
|
||||||
count: 0,
|
count: 0,
|
||||||
max: 3,
|
max: 3,
|
||||||
delay: 1000,
|
delay: 2000,
|
||||||
|
includes: ["[524]"],
|
||||||
},
|
},
|
||||||
config.retry
|
config.retry
|
||||||
);
|
);
|
||||||
@@ -261,7 +283,7 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
|
|||||||
logger.error(`请求出错:${errorMessage} status:${status},statusText:${error.response?.statusText || error.code},url:${error.config?.url},method:${error.config?.method}。`);
|
logger.error(`请求出错:${errorMessage} status:${status},statusText:${error.response?.statusText || error.code},url:${error.config?.url},method:${error.config?.method}。`);
|
||||||
logger.error("返回数据:", JSON.stringify(error.response?.data));
|
logger.error("返回数据:", JSON.stringify(error.response?.data));
|
||||||
if (error.response?.data) {
|
if (error.response?.data) {
|
||||||
const message = error.response.data.message || error.response.data.msg || error.response.data.error;
|
const message = error.response?.data?.message || error.response?.data?.msg || error.response?.data?.error;
|
||||||
if (typeof message === "string") {
|
if (typeof message === "string") {
|
||||||
error.message = message;
|
error.message = message;
|
||||||
}
|
}
|
||||||
@@ -273,7 +295,19 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
|
|||||||
const originalRequest = error.config || {};
|
const originalRequest = error.config || {};
|
||||||
// logger.info(`config`, originalRequest);
|
// logger.info(`config`, originalRequest);
|
||||||
const retry = originalRequest.retry || {};
|
const retry = originalRequest.retry || {};
|
||||||
if (retry.status && retry.status.includes(status)) {
|
|
||||||
|
const isRetryStatus = retry.status && retry.status.includes(status);
|
||||||
|
let isRetryMessage = false;
|
||||||
|
if (retry.includes) {
|
||||||
|
for (const item of retry.includes) {
|
||||||
|
if (error.message?.includes(item)) {
|
||||||
|
isRetryMessage = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRetryStatus || isRetryMessage) {
|
||||||
if (retry.max > 0 && retry.count < retry.max) {
|
if (retry.max > 0 && retry.count < retry.max) {
|
||||||
// 重试次数增加
|
// 重试次数增加
|
||||||
retry.count++;
|
retry.count++;
|
||||||
@@ -301,6 +335,7 @@ export type HttpClientResponse<R> = any;
|
|||||||
export type HttpRequestConfig<D = any> = {
|
export type HttpRequestConfig<D = any> = {
|
||||||
skipSslVerify?: boolean;
|
skipSslVerify?: boolean;
|
||||||
skipCheckRes?: boolean;
|
skipCheckRes?: boolean;
|
||||||
|
logReq?: boolean;
|
||||||
logParams?: boolean;
|
logParams?: boolean;
|
||||||
logRes?: boolean;
|
logRes?: boolean;
|
||||||
logData?: boolean;
|
logData?: boolean;
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { stringUtils } from "./util.string.js";
|
||||||
|
|
||||||
|
describe("stringUtils", () => {
|
||||||
|
describe("maxLength", () => {
|
||||||
|
it("returns an empty string for empty input", () => {
|
||||||
|
expect(stringUtils.maxLength()).to.equal("");
|
||||||
|
expect(stringUtils.maxLength("")).to.equal("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns the original string when it is within the limit", () => {
|
||||||
|
expect(stringUtils.maxLength("certd", 5)).to.equal("certd");
|
||||||
|
expect(stringUtils.maxLength("certd", 6)).to.equal("certd");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("truncates strings longer than the limit and appends ellipsis", () => {
|
||||||
|
expect(stringUtils.maxLength("certificate", 4)).to.equal("cert...");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("appendTimeSuffix", () => {
|
||||||
|
it("returns an empty string for empty input", () => {
|
||||||
|
expect(stringUtils.appendTimeSuffix()).to.equal("");
|
||||||
|
expect(stringUtils.appendTimeSuffix("")).to.equal("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("appends a millisecond timestamp suffix", () => {
|
||||||
|
const result = stringUtils.appendTimeSuffix("certd");
|
||||||
|
|
||||||
|
expect(result).to.match(/^certd-\d{17}$/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"inlineSourceMap":false,
|
"inlineSourceMap": false,
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
@@ -22,21 +22,11 @@
|
|||||||
"composite": false,
|
"composite": false,
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"typeRoots": [ "./typings", "./node_modules/@types"],
|
"typeRoots": ["./typings", "./node_modules/@types"],
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": false,
|
"isolatedModules": false,
|
||||||
"lib": ["ESNext", "DOM"],
|
"lib": ["ESNext", "DOM"]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*.ts", "src/**/*.json"],
|
||||||
"src/**/*.ts",
|
"exclude": ["*.js", "*.ts", "*.spec.ts", "dist", "node_modules", "src/**/*.test.ts", "test"]
|
||||||
"src/**/*.json"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"*.js",
|
|
||||||
"*.ts",
|
|
||||||
"*.spec.ts",
|
|
||||||
"dist",
|
|
||||||
"node_modules",
|
|
||||||
"test"
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,31 @@
|
|||||||
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.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* cnameProvider域名支持设置子域名托管 ([7266af1](https://github.com/certd/certd/commit/7266af17491a98338022cfb18cfedfb93ca6ef8f))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 524错误时重试3次 ([00e6d58](https://github.com/certd/certd/commit/00e6d580c2f54af70fe96a214aff87c4b96426c2))
|
||||||
|
* 优化流水线执行时的状态保存性能 ([e00830b](https://github.com/certd/certd/commit/e00830bebcfe6344499e490bc174de96f9fb22d6))
|
||||||
|
|
||||||
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/pipeline",
|
"name": "@certd/pipeline",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.11",
|
"version": "1.39.15",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -14,12 +14,13 @@
|
|||||||
"build3": "rollup -c",
|
"build3": "rollup -c",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "mocha --loader=ts-node/esm",
|
"test": "mocha --loader=ts-node/esm",
|
||||||
|
"test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||||
"pub": "npm publish",
|
"pub": "npm publish",
|
||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.39.11",
|
"@certd/basic": "^1.39.15",
|
||||||
"@certd/plus-core": "^1.39.11",
|
"@certd/plus-core": "^1.39.15",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"reflect-metadata": "^0.1.13"
|
"reflect-metadata": "^0.1.13"
|
||||||
@@ -36,14 +37,17 @@
|
|||||||
"@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",
|
||||||
"chai": "4.3.10",
|
"chai": "4.3.10",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.41.0",
|
"eslint": "^8.41.0",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"esmock": "^2.7.5",
|
||||||
"mocha": "^10.2.0",
|
"mocha": "^10.2.0",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
"gitHead": "bae5a04dcc0a679c290a9805c3ac4a6020eb6ec0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export type ExecutorOptions = {
|
|||||||
pipeline: Pipeline;
|
pipeline: Pipeline;
|
||||||
storage: IStorage;
|
storage: IStorage;
|
||||||
onChanged: (history: RunHistory) => Promise<void>;
|
onChanged: (history: RunHistory) => Promise<void>;
|
||||||
|
onFinished: (history: RunHistory) => Promise<void>;
|
||||||
accessService: IAccessService;
|
accessService: IAccessService;
|
||||||
emailService: IEmailService;
|
emailService: IEmailService;
|
||||||
notificationService: INotificationService;
|
notificationService: INotificationService;
|
||||||
@@ -47,16 +48,19 @@ export class Executor {
|
|||||||
lastRuntime!: RunHistory;
|
lastRuntime!: RunHistory;
|
||||||
options: ExecutorOptions;
|
options: ExecutorOptions;
|
||||||
abort: AbortController = new AbortController();
|
abort: AbortController = new AbortController();
|
||||||
|
|
||||||
_inited = false;
|
_inited = false;
|
||||||
|
|
||||||
onChanged: (history: RunHistory) => Promise<void>;
|
onChanged: (history: RunHistory) => Promise<void>;
|
||||||
|
onFinished: (history: RunHistory) => Promise<void>;
|
||||||
constructor(options: ExecutorOptions) {
|
constructor(options: ExecutorOptions) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.pipeline = cloneDeep(options.pipeline);
|
this.pipeline = cloneDeep(options.pipeline);
|
||||||
this.onChanged = async (history: RunHistory) => {
|
this.onChanged = async (history: RunHistory) => {
|
||||||
await options.onChanged(history);
|
await options.onChanged(history);
|
||||||
};
|
};
|
||||||
|
this.onFinished = async (history: RunHistory) => {
|
||||||
|
await options.onFinished(history);
|
||||||
|
};
|
||||||
this.pipeline.userId = options.user.id;
|
this.pipeline.userId = options.user.id;
|
||||||
this.contextFactory = new ContextFactory(options.storage);
|
this.contextFactory = new ContextFactory(options.storage);
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -77,7 +81,7 @@ export class Executor {
|
|||||||
async cancel() {
|
async cancel() {
|
||||||
this.abort.abort();
|
this.abort.abort();
|
||||||
this.runtime?.cancel(this.pipeline);
|
this.runtime?.cancel(this.pipeline);
|
||||||
await this.onChanged(this.runtime);
|
await this.onFinished(this.runtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(runtimeId: any = 0, triggerType: string) {
|
async run(runtimeId: any = 0, triggerType: string) {
|
||||||
@@ -111,7 +115,7 @@ export class Executor {
|
|||||||
this.logger.error("pipeline 执行失败", e);
|
this.logger.error("pipeline 执行失败", e);
|
||||||
} finally {
|
} finally {
|
||||||
clearInterval(intervalFlushLogId);
|
clearInterval(intervalFlushLogId);
|
||||||
await this.onChanged(this.runtime);
|
await this.onFinished(this.runtime);
|
||||||
//保存之前移除logs
|
//保存之前移除logs
|
||||||
const lastRuntime: any = {
|
const lastRuntime: any = {
|
||||||
...this.runtime,
|
...this.runtime,
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export type Notification = {
|
|||||||
options?: EmailOptions;
|
options?: EmailOptions;
|
||||||
notificationId: number;
|
notificationId: number;
|
||||||
title: string;
|
title: string;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Pipeline = Runnable & {
|
export type Pipeline = Runnable & {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { PluginGroup, pluginGroups } from "./group.js";
|
||||||
|
|
||||||
|
describe("PluginGroup", () => {
|
||||||
|
it("initializes a group with defaults", () => {
|
||||||
|
const group = new PluginGroup("custom", "Custom");
|
||||||
|
|
||||||
|
expect(group.key).to.equal("custom");
|
||||||
|
expect(group.title).to.equal("Custom");
|
||||||
|
expect(group.order).to.equal(0);
|
||||||
|
expect(group.icon).to.equal("");
|
||||||
|
expect(group.plugins).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exposes built-in groups with stable keys", () => {
|
||||||
|
expect(pluginGroups.cert.key).to.equal("cert");
|
||||||
|
expect(pluginGroups.host.key).to.equal("host");
|
||||||
|
expect(pluginGroups.other.order).to.equal(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,6 +3,7 @@ import { IAccess } from "../access/index.js";
|
|||||||
export type CnameProvider = {
|
export type CnameProvider = {
|
||||||
id: any;
|
id: any;
|
||||||
domain: string;
|
domain: string;
|
||||||
|
subdomain?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
dnsProviderType?: string;
|
dnsProviderType?: string;
|
||||||
access?: IAccess;
|
access?: IAccess;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"inlineSourceMap":false,
|
"inlineSourceMap": false,
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
@@ -22,22 +22,11 @@
|
|||||||
"composite": false,
|
"composite": false,
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
// "sourceMap": true,
|
// "sourceMap": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": false,
|
"isolatedModules": false,
|
||||||
"lib": ["ESNext", "DOM"],
|
"lib": ["ESNext", "DOM"]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.json"],
|
||||||
"src/**/*.ts",
|
"exclude": ["*.js", "*.ts", "*.spec.ts", "dist", "node_modules", "src/**/*.test.ts", "test"]
|
||||||
"src/**/*.d.ts",
|
|
||||||
"src/**/*.json"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"*.js",
|
|
||||||
"*.ts",
|
|
||||||
"*.spec.ts",
|
|
||||||
"dist",
|
|
||||||
"node_modules",
|
|
||||||
"test"
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
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.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
**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.39.11",
|
"version": "1.39.15",
|
||||||
"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",
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
"build": "npm run before-build && rollup -c ",
|
"build": "npm run before-build && rollup -c ",
|
||||||
"dev-build": "npm run build",
|
"dev-build": "npm run build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||||
"pub": "npm publish"
|
"pub": "npm publish"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -21,8 +22,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@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",
|
||||||
|
"esmock": "^2.7.5",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"tslib": "^2.8.1"
|
"tslib": "^2.8.1"
|
||||||
},
|
},
|
||||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
"gitHead": "bae5a04dcc0a679c290a9805c3ac4a6020eb6ec0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
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.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
**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.39.11",
|
"version": "1.39.15",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"build3": "rollup -c",
|
"build3": "rollup -c",
|
||||||
"build2": "vue-tsc --noEmit && vite build",
|
"build2": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||||
"pub": "npm publish"
|
"pub": "npm publish"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -23,13 +24,15 @@
|
|||||||
"@types/chai": "^4.3.3",
|
"@types/chai": "^4.3.3",
|
||||||
"@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",
|
||||||
"eslint": "^8.24.0",
|
"eslint": "^8.24.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"esmock": "^2.7.5",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
"gitHead": "bae5a04dcc0a679c290a9805c3ac4a6020eb6ec0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
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.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
**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.39.11",
|
"version": "1.39.15",
|
||||||
"description": "jdcloud openApi sdk",
|
"description": "jdcloud openApi sdk",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c ",
|
"build": "rollup -c ",
|
||||||
"dev-build": "npm run build",
|
"dev-build": "npm run build",
|
||||||
|
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||||
"pub": "npm publish"
|
"pub": "npm publish"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -29,7 +30,8 @@
|
|||||||
"@typescript-eslint/parser": "^8.26.1",
|
"@typescript-eslint/parser": "^8.26.1",
|
||||||
"chai": "^4.1.2",
|
"chai": "^4.1.2",
|
||||||
"config": "^1.30.0",
|
"config": "^1.30.0",
|
||||||
"cross-env": "^5.1.4",
|
"cross-env": "^7.0.3",
|
||||||
|
"esmock": "^2.7.5",
|
||||||
"js-yaml": "^3.11.0",
|
"js-yaml": "^3.11.0",
|
||||||
"mocha": "^5.0.0",
|
"mocha": "^5.0.0",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
@@ -56,5 +58,5 @@
|
|||||||
"fetch"
|
"fetch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
"gitHead": "bae5a04dcc0a679c290a9805c3ac4a6020eb6ec0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
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.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
**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.39.11",
|
"version": "1.39.15",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -14,24 +14,27 @@
|
|||||||
"build3": "rollup -c",
|
"build3": "rollup -c",
|
||||||
"build2": "vue-tsc --noEmit && vite build",
|
"build2": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||||
"pub": "npm publish",
|
"pub": "npm publish",
|
||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.39.11",
|
"@certd/basic": "^1.39.15",
|
||||||
"@kubernetes/client-node": "0.21.0"
|
"@kubernetes/client-node": "0.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.3.3",
|
"@types/chai": "^4.3.3",
|
||||||
"@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",
|
||||||
"eslint": "^8.24.0",
|
"eslint": "^8.24.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"esmock": "^2.7.5",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
"gitHead": "bae5a04dcc0a679c290a9805c3ac4a6020eb6ec0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,32 @@
|
|||||||
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.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复启动时报密钥备份不存在的问题 ([c966896](https://github.com/certd/certd/commit/c9668965226af6b54e0e576931dcba8b3d188ef3))
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **设置:** 添加首页启用开关配置 ([25ad1e6](https://github.com/certd/certd/commit/25ad1e6f861e43288cc8bd90d4903628e6faec29))
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 增加权威NS检查开关,某些用户服务器禁止向黑名单NS服务器发请求 ([1aa50cf](https://github.com/certd/certd/commit/1aa50cf53a0deab752f35ec973912e41ab8161b6))
|
||||||
|
* 支持页脚自定义 ([c985a13](https://github.com/certd/certd/commit/c985a13544aa31b0eb0783f9a3193a7e8bdc6ed6))
|
||||||
|
|
||||||
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-server",
|
"name": "@certd/lib-server",
|
||||||
"version": "1.39.11",
|
"version": "1.39.15",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"build": "npm run before-build && tsc --skipLibCheck",
|
"build": "npm run before-build && tsc --skipLibCheck",
|
||||||
"dev-build": "npm run build",
|
"dev-build": "npm run build",
|
||||||
"test": "midway-bin test --ts -V",
|
"test": "midway-bin test --ts -V",
|
||||||
|
"test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||||
"test1": "midway-bin test --ts -V -f test/blank.test.ts -t 'hash-check'",
|
"test1": "midway-bin test --ts -V -f test/blank.test.ts -t 'hash-check'",
|
||||||
"cov": "midway-bin cov --ts",
|
"cov": "midway-bin cov --ts",
|
||||||
"lint": "mwts check",
|
"lint": "mwts check",
|
||||||
@@ -28,11 +29,11 @@
|
|||||||
],
|
],
|
||||||
"license": "AGPL",
|
"license": "AGPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.39.11",
|
"@certd/acme-client": "^1.39.15",
|
||||||
"@certd/basic": "^1.39.11",
|
"@certd/basic": "^1.39.15",
|
||||||
"@certd/pipeline": "^1.39.11",
|
"@certd/pipeline": "^1.39.15",
|
||||||
"@certd/plugin-lib": "^1.39.11",
|
"@certd/plugin-lib": "^1.39.15",
|
||||||
"@certd/plus-core": "^1.39.11",
|
"@certd/plus-core": "^1.39.15",
|
||||||
"@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",
|
||||||
@@ -43,7 +44,6 @@
|
|||||||
"@midwayjs/upload": "3.20.13",
|
"@midwayjs/upload": "3.20.13",
|
||||||
"@midwayjs/validate": "3.20.13",
|
"@midwayjs/validate": "3.20.13",
|
||||||
"better-sqlite3": "^11.1.2",
|
"better-sqlite3": "^11.1.2",
|
||||||
"cross-env": "^7.0.3",
|
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mwts": "^1.3.0",
|
"mwts": "^1.3.0",
|
||||||
@@ -52,17 +52,22 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.3.3",
|
"@types/chai": "^4.3.3",
|
||||||
|
"@types/mocha": "^10.0.1",
|
||||||
"@types/node": "^18",
|
"@types/node": "^18",
|
||||||
"@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",
|
||||||
"eslint": "^8.24.0",
|
"eslint": "^8.24.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"esmock": "^2.7.5",
|
||||||
|
"mocha": "^10.2.0",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typeorm": "^0.3.11",
|
"typeorm": "^0.3.11",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
"gitHead": "bae5a04dcc0a679c290a9805c3ac4a6020eb6ec0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { Constants } from "./constants.js";
|
||||||
|
import { ParamException } from "./exception/param-exception.js";
|
||||||
|
import { Result } from "./result.js";
|
||||||
|
|
||||||
|
describe("lib-server basic helpers", () => {
|
||||||
|
it("builds success and error results", () => {
|
||||||
|
const success = Result.success("ok", { id: 1 });
|
||||||
|
assert.ok(success instanceof Result);
|
||||||
|
assert.equal(success.code, 0);
|
||||||
|
assert.equal(success.message, "ok");
|
||||||
|
assert.deepEqual(success.data, { id: 1 });
|
||||||
|
|
||||||
|
const error = Result.error(400, "bad request");
|
||||||
|
assert.ok(error instanceof Result);
|
||||||
|
assert.equal(error.code, 400);
|
||||||
|
assert.equal(error.message, "bad request");
|
||||||
|
assert.equal(error.data, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses default param exception metadata", () => {
|
||||||
|
const error = new ParamException(undefined);
|
||||||
|
|
||||||
|
assert.equal(error.name, "ParamException");
|
||||||
|
assert.equal(error.code, Constants.res.param.code);
|
||||||
|
assert.equal(error.message, Constants.res.param.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -29,8 +29,10 @@ export class SysPublicSettings extends BaseSettings {
|
|||||||
managerOtherUserPipeline = false;
|
managerOtherUserPipeline = false;
|
||||||
icpNo?: string;
|
icpNo?: string;
|
||||||
mpsNo?: string;
|
mpsNo?: string;
|
||||||
|
customFooter?: string;
|
||||||
robots?: boolean = true;
|
robots?: boolean = true;
|
||||||
aiChatEnabled = true;
|
aiChatEnabled = true;
|
||||||
|
homePageEnabled = true;
|
||||||
|
|
||||||
|
|
||||||
//验证码是否开启
|
//验证码是否开启
|
||||||
@@ -78,6 +80,7 @@ export class SysPrivateSettings extends BaseSettings {
|
|||||||
|
|
||||||
httpsProxy? = '';
|
httpsProxy? = '';
|
||||||
httpProxy? = '';
|
httpProxy? = '';
|
||||||
|
commonHeaders?: string = '';
|
||||||
|
|
||||||
reverseProxies?: Record<string, string> = {};
|
reverseProxies?: Record<string, string> = {};
|
||||||
|
|
||||||
@@ -92,6 +95,9 @@ export class SysPrivateSettings extends BaseSettings {
|
|||||||
environmentVars?: string = '';
|
environmentVars?: string = '';
|
||||||
|
|
||||||
|
|
||||||
|
acmeWalkFromAuthoritative?: boolean = true;
|
||||||
|
|
||||||
|
|
||||||
sms?: {
|
sms?: {
|
||||||
type?: string;
|
type?: string;
|
||||||
config?: any;
|
config?: any;
|
||||||
@@ -267,4 +273,3 @@ export class SysSafeSetting extends BaseSettings {
|
|||||||
autoHiddenTimes: 5,
|
autoHiddenTimes: 5,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { Repository } from 'typeorm';
|
|||||||
import { SysSettingsEntity } from '../entity/sys-settings.js';
|
import { SysSettingsEntity } from '../entity/sys-settings.js';
|
||||||
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js';
|
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js';
|
||||||
|
|
||||||
import { getAllSslProviderDomains, setSslProviderReverseProxies } from '@certd/acme-client';
|
import { getAllSslProviderDomains, setSslProviderReverseProxies, setWalkFromAuthoritative } from '@certd/acme-client';
|
||||||
import { cache, logger, mergeUtils, setGlobalProxy } from '@certd/basic';
|
import { cache, logger, mergeUtils, setGlobalHeaders, setGlobalProxy } from '@certd/basic';
|
||||||
import { isPlus } from '@certd/plus-core';
|
import { isPlus } from '@certd/plus-core';
|
||||||
import * as dns from 'node:dns';
|
import * as dns from 'node:dns';
|
||||||
import { BaseService, setAdminMode } from '../../../basic/index.js';
|
import { BaseService, setAdminMode } from '../../../basic/index.js';
|
||||||
@@ -167,6 +167,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||||||
httpsProxy: privateSetting.httpsProxy,
|
httpsProxy: privateSetting.httpsProxy,
|
||||||
};
|
};
|
||||||
setGlobalProxy(opts);
|
setGlobalProxy(opts);
|
||||||
|
setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders));
|
||||||
|
|
||||||
if (privateSetting.dnsResultOrder) {
|
if (privateSetting.dnsResultOrder) {
|
||||||
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
|
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
|
||||||
@@ -180,14 +181,17 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||||||
|
|
||||||
//加载环境变量
|
//加载环境变量
|
||||||
this.setEnvironmentVars(privateSetting.environmentVars);
|
this.setEnvironmentVars(privateSetting.environmentVars);
|
||||||
|
|
||||||
|
setWalkFromAuthoritative(privateSetting.acmeWalkFromAuthoritative);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setEnvironmentVars(vars: string) {
|
parseKeyValueText(text: string) {
|
||||||
const envVars = {}
|
const values = {};
|
||||||
if (typeof vars !== 'string') {
|
if (typeof text !== 'string') {
|
||||||
vars = ""
|
text = "";
|
||||||
}
|
}
|
||||||
vars.split('\n').forEach(line => {
|
text.split('\n').forEach(line => {
|
||||||
line = line.trim();
|
line = line.trim();
|
||||||
if (!line || line.startsWith('#')) {
|
if (!line || line.startsWith('#')) {
|
||||||
return
|
return
|
||||||
@@ -201,11 +205,18 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const [key, value] = line.split('=');
|
const eqIndex = line.indexOf('=');
|
||||||
|
const key = line.substring(0, eqIndex).trim();
|
||||||
|
const value = line.substring(eqIndex + 1).trim();
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
envVars[key.trim()] = value.trim();
|
values[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnvironmentVars(vars: string) {
|
||||||
|
const envVars = this.parseKeyValueText(vars);
|
||||||
//先删除旧环境变量
|
//先删除旧环境变量
|
||||||
if (lastSaveEnvVars) {
|
if (lastSaveEnvVars) {
|
||||||
for (const key in lastSaveEnvVars) {
|
for (const key in lastSaveEnvVars) {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { AccessService } from "./access-service.js";
|
||||||
|
|
||||||
|
describe("AccessService", () => {
|
||||||
|
it("does not write id into access setting when updating selected fields", async () => {
|
||||||
|
let updateParam: any;
|
||||||
|
const service = new AccessService();
|
||||||
|
service.info = async () => ({
|
||||||
|
id: 12,
|
||||||
|
type: "eab",
|
||||||
|
} as any);
|
||||||
|
service.decryptAccessEntity = () => ({
|
||||||
|
kid: "kid-1",
|
||||||
|
});
|
||||||
|
service.update = async (param: any) => {
|
||||||
|
updateParam = param;
|
||||||
|
return param;
|
||||||
|
};
|
||||||
|
|
||||||
|
await service.updateAccess({
|
||||||
|
id: 12,
|
||||||
|
accountKey: "account-key",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(JSON.parse(updateParam.setting), {
|
||||||
|
kid: "kid-1",
|
||||||
|
accountKey: "account-key",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -123,6 +123,25 @@ export class AccessService extends BaseService<AccessEntity> {
|
|||||||
return await super.update(param);
|
return await super.update(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateAccess(access: any) {
|
||||||
|
const oldEntity = await this.info(access.id);
|
||||||
|
if (oldEntity == null) {
|
||||||
|
throw new ValidateException('该授权配置不存在,请确认是否已被删除');
|
||||||
|
}
|
||||||
|
const setting = this.decryptAccessEntity(oldEntity);
|
||||||
|
for (const key of Object.keys(access)) {
|
||||||
|
if (key === 'id') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
setting[key] = access[key];
|
||||||
|
}
|
||||||
|
return await this.update({
|
||||||
|
id: access.id,
|
||||||
|
type: oldEntity.type,
|
||||||
|
setting: JSON.stringify(setting),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getSimpleInfo(id: number) {
|
async getSimpleInfo(id: number) {
|
||||||
const entity = await this.info(id);
|
const entity = await this.info(id);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
import { Encryptor, SysSecret, SysSettingsService } from '../../../system/index.js';
|
import { Encryptor, SysSecret, SysSettingsService } from '../../../system/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,8 +12,7 @@ export class EncryptService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
sysSettingService: SysSettingsService;
|
sysSettingService: SysSettingsService;
|
||||||
|
|
||||||
@Init()
|
async doInit() {
|
||||||
async init() {
|
|
||||||
const secret: SysSecret = await this.sysSettingService.getSecret();
|
const secret: SysSecret = await this.sysSettingService.getSecret();
|
||||||
this.encryptor = new Encryptor(secret.encryptSecret);
|
this.encryptor = new Encryptor(secret.encryptSecret);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"inlineSourceMap":false,
|
"inlineSourceMap": false,
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
@@ -17,25 +17,15 @@
|
|||||||
"pretty": true,
|
"pretty": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"typeRoots": [ "./typings", "./node_modules/@types"],
|
"typeRoots": ["./typings", "./node_modules/@types"],
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"composite": false,
|
"composite": false,
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"lib": ["ESNext", "DOM"],
|
"lib": ["ESNext", "DOM"]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.json"],
|
||||||
"src/**/*.ts",
|
"exclude": ["*.js", "*.ts", "dist", "node_modules", "src/**/*.test.ts", "test"]
|
||||||
"src/**/*.d.ts",
|
|
||||||
"src/**/*.json"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"*.js",
|
|
||||||
"*.ts",
|
|
||||||
"dist",
|
|
||||||
"node_modules",
|
|
||||||
"test"
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
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.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
**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.39.11",
|
"version": "1.39.15",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
"dev-build": "npm run build",
|
"dev-build": "npm run build",
|
||||||
"test": "midway-bin test --ts -V",
|
"test": "midway-bin test --ts -V",
|
||||||
"test1": "midway-bin test --ts -V -f test/blank.test.ts -t 'hash-check'",
|
"test1": "midway-bin test --ts -V -f test/blank.test.ts -t 'hash-check'",
|
||||||
|
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||||
"cov": "midway-bin cov --ts",
|
"cov": "midway-bin cov --ts",
|
||||||
"prepublish": "npm run build",
|
"prepublish": "npm run build",
|
||||||
"pub": "npm publish"
|
"pub": "npm publish"
|
||||||
@@ -35,16 +36,18 @@
|
|||||||
"@types/node": "^18",
|
"@types/node": "^18",
|
||||||
"@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",
|
||||||
"eslint": "^8.24.0",
|
"eslint": "^8.24.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"esmock": "^2.7.5",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typeorm": "^0.3.11",
|
"typeorm": "^0.3.11",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
"gitHead": "bae5a04dcc0a679c290a9805c3ac4a6020eb6ec0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
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.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|
||||||
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
**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.39.11",
|
"version": "1.39.15",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -13,14 +13,15 @@
|
|||||||
"build3": "rollup -c",
|
"build3": "rollup -c",
|
||||||
"build2": "vue-tsc --noEmit && vite build",
|
"build2": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||||
"pub": "npm publish",
|
"pub": "npm publish",
|
||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.39.11",
|
"@certd/acme-client": "^1.39.15",
|
||||||
"@certd/basic": "^1.39.11",
|
"@certd/basic": "^1.39.15",
|
||||||
"@certd/pipeline": "^1.39.11",
|
"@certd/pipeline": "^1.39.15",
|
||||||
"@certd/plugin-lib": "^1.39.11",
|
"@certd/plugin-lib": "^1.39.15",
|
||||||
"psl": "^1.9.0",
|
"psl": "^1.9.0",
|
||||||
"punycode.js": "^2.3.1"
|
"punycode.js": "^2.3.1"
|
||||||
},
|
},
|
||||||
@@ -30,13 +31,15 @@
|
|||||||
"@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",
|
||||||
"chai": "^4.3.6",
|
"chai": "^4.3.6",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.24.0",
|
"eslint": "^8.24.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"esmock": "^2.7.5",
|
||||||
"mocha": "^10.1.0",
|
"mocha": "^10.1.0",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
"gitHead": "bae5a04dcc0a679c290a9805c3ac4a6020eb6ec0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
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.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|
||||||
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-lib",
|
"name": "@certd/plugin-lib",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.11",
|
"version": "1.39.15",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
"build3": "rollup -c",
|
"build3": "rollup -c",
|
||||||
"build2": "vue-tsc --noEmit && vite build",
|
"build2": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
"test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||||
"pub": "npm publish",
|
"pub": "npm publish",
|
||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
@@ -22,10 +23,10 @@
|
|||||||
"@alicloud/pop-core": "^1.7.10",
|
"@alicloud/pop-core": "^1.7.10",
|
||||||
"@alicloud/tea-util": "^1.4.11",
|
"@alicloud/tea-util": "^1.4.11",
|
||||||
"@aws-sdk/client-s3": "^3.964.0",
|
"@aws-sdk/client-s3": "^3.964.0",
|
||||||
"@certd/acme-client": "^1.39.11",
|
"@certd/acme-client": "^1.39.15",
|
||||||
"@certd/basic": "^1.39.11",
|
"@certd/basic": "^1.39.15",
|
||||||
"@certd/pipeline": "^1.39.11",
|
"@certd/pipeline": "^1.39.15",
|
||||||
"@certd/plus-core": "^1.39.11",
|
"@certd/plus-core": "^1.39.15",
|
||||||
"@kubernetes/client-node": "0.21.0",
|
"@kubernetes/client-node": "0.21.0",
|
||||||
"ali-oss": "^6.22.0",
|
"ali-oss": "^6.22.0",
|
||||||
"basic-ftp": "^5.0.5",
|
"basic-ftp": "^5.0.5",
|
||||||
@@ -49,13 +50,16 @@
|
|||||||
"@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",
|
||||||
"chai": "^4.3.6",
|
"chai": "^4.3.6",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.24.0",
|
"eslint": "^8.24.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"esmock": "^2.7.5",
|
||||||
"mocha": "^10.1.0",
|
"mocha": "^10.1.0",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
"gitHead": "bae5a04dcc0a679c290a9805c3ac4a6020eb6ec0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "./util.js";
|
||||||
|
|
||||||
|
describe("plugin-lib common util", () => {
|
||||||
|
it("builds cert domain getter input define with defaults", () => {
|
||||||
|
const define = createCertDomainGetterInputDefine();
|
||||||
|
|
||||||
|
expect(define.title).to.equal("当前证书域名");
|
||||||
|
expect(define.component.name).to.equal("cert-domains-getter");
|
||||||
|
expect(define.required).to.equal(true);
|
||||||
|
expect(define.template).to.equal(false);
|
||||||
|
expect(define.mergeScript).to.contain("form.cert");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows overriding cert input key and props", () => {
|
||||||
|
const define = createCertDomainGetterInputDefine({
|
||||||
|
certInputKey: "customCert",
|
||||||
|
props: {
|
||||||
|
title: "自定义域名",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(define.title).to.equal("自定义域名");
|
||||||
|
expect(define.required).to.equal(false);
|
||||||
|
expect(define.mergeScript).to.contain("form.customCert");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds remote select input define with expected component options", () => {
|
||||||
|
const define = createRemoteSelectInputDefine({
|
||||||
|
title: "选择资源",
|
||||||
|
action: "ListResource",
|
||||||
|
typeName: "resource",
|
||||||
|
multi: false,
|
||||||
|
search: true,
|
||||||
|
watches: ["region"],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(define.title).to.equal("选择资源");
|
||||||
|
expect(define.required).to.equal(true);
|
||||||
|
expect(define.component).to.include({
|
||||||
|
name: "remote-select",
|
||||||
|
vModel: "value",
|
||||||
|
action: "ListResource",
|
||||||
|
typeName: "resource",
|
||||||
|
mode: "default",
|
||||||
|
multi: false,
|
||||||
|
search: true,
|
||||||
|
});
|
||||||
|
expect(define.component.watches).to.deep.equal(["certDomains", "accessId", "region"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"inlineSourceMap":false,
|
"inlineSourceMap": false,
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
@@ -16,27 +16,17 @@
|
|||||||
"pretty": true,
|
"pretty": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"typeRoots": [ "./typings", "./node_modules/@types"],
|
"typeRoots": ["./typings", "./node_modules/@types"],
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"composite": false,
|
"composite": false,
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
// "sourceMap": true,
|
// "sourceMap": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": false,
|
"isolatedModules": false,
|
||||||
"lib": ["ESNext", "DOM"],
|
"lib": ["ESNext", "DOM"]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.json"],
|
||||||
"src/**/*.ts",
|
"exclude": ["*.js", "*.ts", "dist", "node_modules", "src/**/*.test.ts", "test"]
|
||||||
"src/**/*.d.ts",
|
|
||||||
"src/**/*.json"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"*.js",
|
|
||||||
"*.ts",
|
|
||||||
"dist",
|
|
||||||
"node_modules",
|
|
||||||
"test"
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-5
@@ -1,4 +1,4 @@
|
|||||||
FROM node:22-alpine AS builder
|
FROM node:22-alpine3.21 AS builder
|
||||||
|
|
||||||
# RUN apk add build-base
|
# RUN apk add build-base
|
||||||
# RUN wget -O - https://github.com/jemalloc/jemalloc/releases/download/5.3.0/jemalloc-5.3.0.tar.bz2 | tar -xj && \
|
# RUN wget -O - https://github.com/jemalloc/jemalloc/releases/download/5.3.0/jemalloc-5.3.0.tar.bz2 | tar -xj && \
|
||||||
@@ -10,17 +10,22 @@ FROM node:22-alpine AS builder
|
|||||||
|
|
||||||
WORKDIR /workspace/
|
WORKDIR /workspace/
|
||||||
COPY . /workspace/
|
COPY . /workspace/
|
||||||
# armv7 目前只能用node18, pnpm9不支持node18,所以pnpm只能用8.15.7版本
|
# pnpm v11打包会报错([ERR_PNPM_IGNORED_BUILDS] Ignored build scripts),暂时固定10.33.4版本。
|
||||||
# https://github.com/nodejs/docker-node/issues/1946
|
# https://pnpm.io/zh/migration
|
||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm@10.33.4
|
||||||
|
|
||||||
#RUN cd /workspace/certd-client && pnpm install && npm run build
|
#RUN cd /workspace/certd-client && pnpm install && npm run build
|
||||||
RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf
|
RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf
|
||||||
RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker
|
RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker
|
||||||
|
|
||||||
|
# RUN cd /workspace/certd-server && \
|
||||||
|
# pnpm install --ignore-scripts && \
|
||||||
|
# yes | pnpm approve-builds && \
|
||||||
|
# pnpm rebuild && \
|
||||||
|
# npm run build-on-docker
|
||||||
|
|
||||||
|
|
||||||
FROM node:22-alpine
|
FROM node:22-alpine3.21
|
||||||
EXPOSE 7001
|
EXPOSE 7001
|
||||||
EXPOSE 7002
|
EXPOSE 7002
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,47 @@
|
|||||||
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.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* icon选择器增加一套logo集 ([fdd5848](https://github.com/certd/certd/commit/fdd5848df4055a6ee07dc5eabaaf6b718672882d))
|
||||||
|
* **monitor/site:** 新增站点监控页面禁用启用、检查状态两个筛选条件 ([118c15d](https://github.com/certd/certd/commit/118c15d04633a6ef06f2d9e7a7849d20f596e02c))
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复阿里云订阅流水线创建对话框无法获取阿里订单列表的bug ([a362860](https://github.com/certd/certd/commit/a362860137bfb7072893c844fe775edc46070ee1))
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* cnameProvider域名支持设置子域名托管 ([7266af1](https://github.com/certd/certd/commit/7266af17491a98338022cfb18cfedfb93ca6ef8f))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **设置:** 添加首页启用开关配置 ([25ad1e6](https://github.com/certd/certd/commit/25ad1e6f861e43288cc8bd90d4903628e6faec29))
|
||||||
|
* **用户资料:** 新增手机号邮箱绑定功能 ([e0eb0e2](https://github.com/certd/certd/commit/e0eb0e21f6dae24b639c944f9aba2c90496ab1c0))
|
||||||
|
* 域名注册过期时间获取再次优化 ([91a1b97](https://github.com/certd/certd/commit/91a1b9755066bf280e194dabf7c3a9f936e2643f))
|
||||||
|
* **证书流水线:** 添加批量更新证书申请参数功能 ([63be1c1](https://github.com/certd/certd/commit/63be1c1cbd9b09a3b48f26130c296b1cedcca1ac))
|
||||||
|
* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86))
|
||||||
|
* **domain:** 添加域名过期时间同步进度显示功能 ([9d2937d](https://github.com/certd/certd/commit/9d2937dd4b14ffab73e9b096edd2aa8539811182))
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 调整手机版首页标题被挤开的bug ([eab66e2](https://github.com/certd/certd/commit/eab66e2d1988635985745f2d1b227b958969ee00))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 524错误时重试3次 ([00e6d58](https://github.com/certd/certd/commit/00e6d580c2f54af70fe96a214aff87c4b96426c2))
|
||||||
|
* 增加权威NS检查开关,某些用户服务器禁止向黑名单NS服务器发请求 ([1aa50cf](https://github.com/certd/certd/commit/1aa50cf53a0deab752f35ec973912e41ab8161b6))
|
||||||
|
* 支持页脚自定义 ([c985a13](https://github.com/certd/certd/commit/c985a13544aa31b0eb0783f9a3193a7e8bdc6ed6))
|
||||||
|
|
||||||
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
## [1.39.11](https://github.com/certd/certd/compare/v1.39.10...v1.39.11) (2026-04-26)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-client",
|
"name": "@certd/ui-client",
|
||||||
"version": "1.39.11",
|
"version": "1.39.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --open",
|
"dev": "vite --open",
|
||||||
@@ -12,7 +12,8 @@
|
|||||||
"debug:force": "vite --force --mode debug",
|
"debug:force": "vite --force --mode debug",
|
||||||
"build": "cross-env NODE_OPTIONS=--max-old-space-size=40960 vite build ",
|
"build": "cross-env NODE_OPTIONS=--max-old-space-size=40960 vite build ",
|
||||||
"dev-build": "echo 1",
|
"dev-build": "echo 1",
|
||||||
"test:unit": "vitest",
|
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||||
|
"test:vue": "vitest run",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"pretty-quick": "pretty-quick",
|
"pretty-quick": "pretty-quick",
|
||||||
@@ -33,11 +34,11 @@
|
|||||||
"@aws-sdk/s3-request-presigner": "^3.964.0",
|
"@aws-sdk/s3-request-presigner": "^3.964.0",
|
||||||
"@certd/vue-js-cron-light": "^4.0.14",
|
"@certd/vue-js-cron-light": "^4.0.14",
|
||||||
"@ctrl/tinycolor": "^4.1.0",
|
"@ctrl/tinycolor": "^4.1.0",
|
||||||
"@fast-crud/editor-code": "^1.27.8",
|
"@fast-crud/editor-code": "^1.28.1",
|
||||||
"@fast-crud/fast-crud": "^1.27.8",
|
"@fast-crud/fast-crud": "^1.28.1",
|
||||||
"@fast-crud/fast-extends": "^1.27.8",
|
"@fast-crud/fast-extends": "^1.28.1",
|
||||||
"@fast-crud/ui-antdv4": "^1.27.8",
|
"@fast-crud/ui-antdv4": "^1.28.1",
|
||||||
"@fast-crud/ui-interface": "^1.27.8",
|
"@fast-crud/ui-interface": "^1.28.1",
|
||||||
"@iconify/tailwind": "^1.2.0",
|
"@iconify/tailwind": "^1.2.0",
|
||||||
"@iconify/vue": "^4.1.1",
|
"@iconify/vue": "^4.1.1",
|
||||||
"@manypkg/get-packages": "^2.2.2",
|
"@manypkg/get-packages": "^2.2.2",
|
||||||
@@ -61,7 +62,6 @@
|
|||||||
"cos-js-sdk-v5": "^1.7.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",
|
||||||
"cross-env": "^7.0.3",
|
|
||||||
"cssnano": "^7.0.6",
|
"cssnano": "^7.0.6",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
@@ -106,8 +106,8 @@
|
|||||||
"zod-defaults": "^0.1.3"
|
"zod-defaults": "^0.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@certd/lib-iframe": "^1.39.11",
|
"@certd/lib-iframe": "^1.39.15",
|
||||||
"@certd/pipeline": "^1.39.11",
|
"@certd/pipeline": "^1.39.15",
|
||||||
"@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",
|
||||||
@@ -126,6 +126,7 @@
|
|||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"caller-path": "^4.0.0",
|
"caller-path": "^4.0.0",
|
||||||
"chai": "^5.1.0",
|
"chai": "^5.1.0",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"dependency-cruiser": "^16.2.3",
|
"dependency-cruiser": "^16.2.3",
|
||||||
"dot": "^1.1.3",
|
"dot": "^1.1.3",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
@@ -135,6 +136,7 @@
|
|||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
"eslint-plugin-promise": "^6.1.1",
|
||||||
"eslint-plugin-vue": "^9.23.0",
|
"eslint-plugin-vue": "^9.23.0",
|
||||||
|
"esmock": "^2.7.5",
|
||||||
"less": "^4.2.0",
|
"less": "^4.2.0",
|
||||||
"less-loader": "^12.2.0",
|
"less-loader": "^12.2.0",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.35",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -1,17 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<a-input :value="valueRef" placeholder="请输入图片验证码" autocomplete="off" @update:value="onChange">
|
<a-input :value="valueRef" :placeholder="t('certd.captcha.inputImageCode')" autocomplete="off" @update:value="onChange">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<fs-icon icon="ion:image-outline"></fs-icon>
|
<fs-icon icon="ion:image-outline"></fs-icon>
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<div class="input-right pointer" title="点击刷新">
|
<div class="input-right pointer" :title="t('certd.captcha.refresh')">
|
||||||
<img class="image-code" :src="imageCodeSrc" @click="resetImageCode" />
|
<img class="image-code" :src="imageCodeSrc" @click="resetImageCode" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from "vue";
|
import { ref, watch } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: any;
|
modelValue: any;
|
||||||
@@ -20,6 +21,7 @@ const props = defineProps<{
|
|||||||
defineOptions({
|
defineOptions({
|
||||||
name: "ImageCaptcha",
|
name: "ImageCaptcha",
|
||||||
});
|
});
|
||||||
|
const { t } = useI18n();
|
||||||
const emit = defineEmits(["update:modelValue", "change"]);
|
const emit = defineEmits(["update:modelValue", "change"]);
|
||||||
|
|
||||||
const valueRef = ref("");
|
const valueRef = ref("");
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
<div class="sweep-animation"></div>
|
<div class="sweep-animation"></div>
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="box-icon">✓</div>
|
<div class="box-icon">✓</div>
|
||||||
<span v-if="modelValue == null" class="status-text">点击进行验证</span>
|
<span v-if="modelValue == null" class="status-text">{{ t("certd.captcha.clickToVerify") }}</span>
|
||||||
<span v-else class="status-text">验证成功</span>
|
<span v-else class="status-text">{{ t("certd.captcha.verifySuccess") }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -13,8 +13,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { notification } from "ant-design-vue";
|
import { notification } from "ant-design-vue";
|
||||||
import { ref, Ref, watch } from "vue";
|
import { ref, Ref, watch } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
import { loadScript } from "vue-plugin-load-script";
|
import { loadScript } from "vue-plugin-load-script";
|
||||||
|
const { t } = useI18n();
|
||||||
const loaded = ref(false);
|
const loaded = ref(false);
|
||||||
async function loadCaptchaScript() {
|
async function loadCaptchaScript() {
|
||||||
// 加载验证码js
|
// 加载验证码js
|
||||||
@@ -56,7 +58,7 @@ function callback(res: { ret: number; ticket: string; randstr: string; errorCode
|
|||||||
|
|
||||||
if (res.errorCode && res.errorCode > 0) {
|
if (res.errorCode && res.errorCode > 0) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: `验证码验证失败:${res.errorMessage || res.errorCode}`,
|
message: t("certd.captcha.verifyFailed", { message: res.errorMessage || res.errorCode }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,13 +85,13 @@ function loadErrorCallback(error: any) {
|
|||||||
// errorMessage: "jsload_error",
|
// errorMessage: "jsload_error",
|
||||||
// });
|
// });
|
||||||
notification.error({
|
notification.error({
|
||||||
message: `验证码加载失败:${error?.message || error}`,
|
message: t("certd.captcha.loadFailed", { message: error?.message || error }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async function triggerCaptcha() {
|
async function triggerCaptcha() {
|
||||||
if (!loaded.value) {
|
if (!loaded.value) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "验证码还未加载完成,请稍后再试",
|
message: t("certd.captcha.notLoaded"),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-11
@@ -4,7 +4,7 @@
|
|||||||
<!-- <td class="domain">-->
|
<!-- <td class="domain">-->
|
||||||
<!-- {{ props.domain }}-->
|
<!-- {{ props.domain }}-->
|
||||||
<!-- </td>-->
|
<!-- </td>-->
|
||||||
<td class="host-record" :title="'域名:' + props.domain">
|
<td class="host-record" :title="t('certd.verifyPlan.domainTitle', { domain: props.domain })">
|
||||||
<fs-copyable v-model="cnameRecord.hostRecord"></fs-copyable>
|
<fs-copyable v-model="cnameRecord.hostRecord"></fs-copyable>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: center">CNAME</td>
|
<td style="text-align: center">CNAME</td>
|
||||||
@@ -16,17 +16,17 @@
|
|||||||
<a-tooltip v-if="cnameRecord.error" :title="cnameRecord.error">
|
<a-tooltip v-if="cnameRecord.error" :title="cnameRecord.error">
|
||||||
<fs-icon class="ml-5 color-red" icon="ion:warning-outline"></fs-icon>
|
<fs-icon class="ml-5 color-red" icon="ion:warning-outline"></fs-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip v-if="cnameRecord.status === 'valid'" title="重置校验状态,重新校验">
|
<a-tooltip v-if="cnameRecord.status === 'valid'" :title="t('certd.verifyPlan.resetStatusTooltip')">
|
||||||
<fs-icon class="ml-2 color-yellow text-md pointer" icon="solar:undo-left-square-bold" @click="resetStatus"></fs-icon>
|
<fs-icon class="ml-2 color-yellow text-md pointer" icon="solar:undo-left-square-bold" @click="resetStatus"></fs-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</td>
|
</td>
|
||||||
<td class="center">
|
<td class="center">
|
||||||
<template v-if="cnameRecord.status !== 'valid'">
|
<template v-if="cnameRecord.status !== 'valid'">
|
||||||
<a-button type="primary" size="small" :loading="loading" @click="doVerify">点击验证</a-button>
|
<a-button type="primary" size="small" :loading="loading" @click="doVerify">{{ t("certd.verifyPlan.clickToValidate") }}</a-button>
|
||||||
<cname-tip :record="cnameRecord"></cname-tip>
|
<cname-tip :record="cnameRecord"></cname-tip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-else class="helper" title="后续自动申请证书需要">不要删除CNAME</div>
|
<div v-else class="helper" :title="t('certd.verifyPlan.keepCnameTitle')">{{ t("certd.verifyPlan.keepCname") }}</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -35,18 +35,20 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { CnameRecord, GetByDomain } from "/@/components/plugins/cert/domains-verify-plan-editor/api";
|
import { CnameRecord, GetByDomain } from "/@/components/plugins/cert/domains-verify-plan-editor/api";
|
||||||
import { ref, watch } from "vue";
|
import { ref, watch } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import { dict } from "@fast-crud/fast-crud";
|
import { dict } from "@fast-crud/fast-crud";
|
||||||
import * as api from "./api.js";
|
import * as api from "./api.js";
|
||||||
import CnameTip from "./cname-tip.vue";
|
import CnameTip from "./cname-tip.vue";
|
||||||
import { Modal } from "ant-design-vue";
|
import { Modal } from "ant-design-vue";
|
||||||
import { utils } from "/@/utils/index.js";
|
import { utils } from "/@/utils/index.js";
|
||||||
|
const { t } = useI18n();
|
||||||
const statusDict = dict({
|
const statusDict = dict({
|
||||||
data: [
|
data: [
|
||||||
{ label: "待设置CNAME", value: "cname", color: "warning" },
|
{ label: t("certd.verifyPlan.status.pendingCname"), value: "cname", color: "warning" },
|
||||||
{ label: "验证中", value: "validating", color: "blue" },
|
{ label: t("certd.verifyPlan.status.validating"), value: "validating", color: "blue" },
|
||||||
{ label: "验证成功", value: "valid", color: "green" },
|
{ label: t("certd.verifyPlan.status.valid"), value: "valid", color: "green" },
|
||||||
{ label: "验证失败", value: "failed", color: "red" },
|
{ label: t("certd.verifyPlan.status.failed"), value: "failed", color: "red" },
|
||||||
{ label: "验证超时", value: "timeout", color: "red" },
|
{ label: t("certd.verifyPlan.status.timeout"), value: "timeout", color: "red" },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -125,8 +127,8 @@ async function doVerify() {
|
|||||||
|
|
||||||
async function resetStatus() {
|
async function resetStatus() {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "重置状态",
|
title: t("certd.verifyPlan.resetStatus"),
|
||||||
content: "确定要重置校验状态吗?",
|
content: t("certd.verifyPlan.confirmResetStatus"),
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
await api.ResetStatus(cnameRecord.value.id);
|
await api.ResetStatus(cnameRecord.value.id);
|
||||||
await loadRecord();
|
await loadRecord();
|
||||||
|
|||||||
+9
-7
@@ -2,17 +2,17 @@
|
|||||||
<a-tooltip :overlay-style="{ maxWidth: '400px' }">
|
<a-tooltip :overlay-style="{ maxWidth: '400px' }">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div>
|
<div>
|
||||||
<div>多试几次,如果仍然无法验证通过,请按如下步骤排查问题:</div>
|
<div>{{ t("certd.verifyPlan.cnameTip.intro") }}</div>
|
||||||
<div>1. 解析记录应该添加在{{ record.domain }}域名下</div>
|
<div>{{ t("certd.verifyPlan.cnameTip.step1", { domain: record.domain }) }}</div>
|
||||||
<div>2. 要添加的是CNAME类型的记录,不是TXT</div>
|
<div>{{ t("certd.verifyPlan.cnameTip.step2") }}</div>
|
||||||
<div>3. 核对记录值是否是:{{ record.recordValue }}</div>
|
<div>{{ t("certd.verifyPlan.cnameTip.step3", { value: record.recordValue }) }}</div>
|
||||||
<div>
|
<div>
|
||||||
4. 在验证中状态下,运行下面的命令,查看cname和txt解析是否正确
|
{{ t("certd.verifyPlan.cnameTip.step4") }}
|
||||||
<fs-copyable :style="{ color: '#52c41a' }" :model-value="nslookupCmd"></fs-copyable>
|
<fs-copyable :style="{ color: '#52c41a' }" :model-value="nslookupCmd"></fs-copyable>
|
||||||
或者
|
{{ t("certd.verifyPlan.cnameTip.or") }}
|
||||||
<fs-copyable :style="{ color: '#52c41a' }" :model-value="digCmd"></fs-copyable>
|
<fs-copyable :style="{ color: '#52c41a' }" :model-value="digCmd"></fs-copyable>
|
||||||
</div>
|
</div>
|
||||||
<div>5. 如果以上检查都没有问题,则可能是DNS解析生效时间比较慢,某些提供商延迟可能高达几个小时</div>
|
<div>{{ t("certd.verifyPlan.cnameTip.step5") }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<fs-icon class="ml-5 pointer" icon="mingcute:question-line"></fs-icon>
|
<fs-icon class="ml-5 pointer" icon="mingcute:question-line"></fs-icon>
|
||||||
@@ -21,9 +21,11 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
record: any;
|
record: any;
|
||||||
}>();
|
}>();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const nslookupCmd = computed(() => {
|
const nslookupCmd = computed(() => {
|
||||||
return `nslookup -q=txt _acme-challenge.${props.record.domain}`;
|
return `nslookup -q=txt _acme-challenge.${props.record.domain}`;
|
||||||
|
|||||||
+8
-5
@@ -2,11 +2,11 @@
|
|||||||
<table class="cname-verify-plan">
|
<table class="cname-verify-plan">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 160px">主机记录</td>
|
<td style="width: 160px">{{ t("certd.verifyPlan.hostRecord") }}</td>
|
||||||
<td style="width: 100px; text-align: center">记录类型</td>
|
<td style="width: 100px; text-align: center">{{ t("certd.verifyPlan.recordType") }}</td>
|
||||||
<td style="width: 250px">请设置CNAME记录(验证成功以后不要删除)</td>
|
<td style="width: 250px">{{ t("certd.verifyPlan.setCnameRecord") }}</td>
|
||||||
<td style="width: 120px" class="center">状态</td>
|
<td style="width: 120px" class="center">{{ t("certd.status") }}</td>
|
||||||
<td style="width: 90px" class="center">操作</td>
|
<td style="width: 90px" class="center">{{ t("certd.verifyPlan.operation") }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<template v-for="key in domains" :key="key">
|
<template v-for="key in domains" :key="key">
|
||||||
@@ -19,11 +19,14 @@
|
|||||||
import { CnameRecord } from "/@/components/plugins/cert/domains-verify-plan-editor/api";
|
import { CnameRecord } from "/@/components/plugins/cert/domains-verify-plan-editor/api";
|
||||||
import CnameRecordInfo from "/@/components/plugins/cert/domains-verify-plan-editor/cname-record-info.vue";
|
import CnameRecordInfo from "/@/components/plugins/cert/domains-verify-plan-editor/cname-record-info.vue";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "CnameVerifyPlan",
|
name: "CnameVerifyPlan",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const emit = defineEmits(["update:modelValue", "change"]);
|
const emit = defineEmits(["update:modelValue", "change"]);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
+8
-5
@@ -2,10 +2,10 @@
|
|||||||
<table class="http-verify-plan">
|
<table class="http-verify-plan">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 160px">网站域名</td>
|
<td style="width: 160px">{{ t("certd.verifyPlan.websiteDomain") }}</td>
|
||||||
<td style="width: 100px; text-align: center">上传方式</td>
|
<td style="width: 100px; text-align: center">{{ t("certd.verifyPlan.uploadMethod") }}</td>
|
||||||
<td style="width: 150px">上传授权</td>
|
<td style="width: 150px">{{ t("certd.verifyPlan.uploadAccess") }}</td>
|
||||||
<td style="width: 200px">网站根目录路径</td>
|
<td style="width: 200px">{{ t("certd.verifyPlan.websiteRootPath") }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody v-if="records" class="http-record-body">
|
<tbody v-if="records" class="http-record-body">
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<access-selector v-model="item.httpUploaderAccess" :type="item.httpUploaderType" @change="onRecordChange"></access-selector>
|
<access-selector v-model="item.httpUploaderAccess" :type="item.httpUploaderType" @change="onRecordChange"></access-selector>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-input v-model:value="item.httpUploadRootDir" placeholder="网站根目录,如:/www/wwwroot" @change="onRecordChange"></a-input>
|
<a-input v-model:value="item.httpUploadRootDir" :placeholder="t('certd.verifyPlan.websiteRootPlaceholder')" @change="onRecordChange"></a-input>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Ref, ref, watch, nextTick } from "vue";
|
import { Ref, ref, watch, nextTick } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import { HttpRecord } from "/@/components/plugins/cert/domains-verify-plan-editor/type";
|
import { HttpRecord } from "/@/components/plugins/cert/domains-verify-plan-editor/type";
|
||||||
import { dict } from "@fast-crud/fast-crud";
|
import { dict } from "@fast-crud/fast-crud";
|
||||||
import { Dicts } from "/@/components/plugins/lib/dicts";
|
import { Dicts } from "/@/components/plugins/lib/dicts";
|
||||||
@@ -39,6 +40,8 @@ defineOptions({
|
|||||||
name: "HttpVerifyPlan",
|
name: "HttpVerifyPlan",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const emit = defineEmits(["update:modelValue", "change"]);
|
const emit = defineEmits(["update:modelValue", "change"]);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
+15
-12
@@ -5,7 +5,7 @@
|
|||||||
<div class="plan-box bg-white dark:bg-neutral-700">
|
<div class="plan-box bg-white dark:bg-neutral-700">
|
||||||
<div class="fullscreen-button pointer flex-center" @click="fullscreen = !fullscreen">
|
<div class="fullscreen-button pointer flex-center" @click="fullscreen = !fullscreen">
|
||||||
<span v-if="!fullscreen" style="font-size: 10px" class="flex-center">
|
<span v-if="!fullscreen" style="font-size: 10px" class="flex-center">
|
||||||
这里可以放大
|
{{ t("certd.verifyPlan.expandTip") }}
|
||||||
<fs-icon icon="ion:arrow-forward-outline"></fs-icon>
|
<fs-icon icon="ion:arrow-forward-outline"></fs-icon>
|
||||||
</span>
|
</span>
|
||||||
<fs-icon :icon="fullscreen ? 'material-symbols:fullscreen-exit' : 'material-symbols:fullscreen'"></fs-icon>
|
<fs-icon :icon="fullscreen ? 'material-symbols:fullscreen-exit' : 'material-symbols:fullscreen'"></fs-icon>
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
<table class="plan-table">
|
<table class="plan-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="min-width: 100px">主域名</th>
|
<th style="min-width: 100px">{{ t("certd.verifyPlan.mainDomain") }}</th>
|
||||||
<th>验证方式</th>
|
<th>{{ t("certd.verifyPlan.challengeType") }}</th>
|
||||||
<th>验证计划</th>
|
<th>{{ t("certd.verifyPlan.challengePlan") }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -30,13 +30,13 @@
|
|||||||
<div class="plan">
|
<div class="plan">
|
||||||
<div v-if="item.type === 'dns'" class="plan-dns">
|
<div v-if="item.type === 'dns'" class="plan-dns">
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<span class="label">DNS类型:</span>
|
<span class="label">{{ t("certd.verifyPlan.dnsType") }}:</span>
|
||||||
<span class="input">
|
<span class="input">
|
||||||
<fs-dict-select
|
<fs-dict-select
|
||||||
v-model:value="item.dnsProviderType"
|
v-model:value="item.dnsProviderType"
|
||||||
size="small"
|
size="small"
|
||||||
:dict="dnsProviderTypeDict"
|
:dict="dnsProviderTypeDict"
|
||||||
placeholder="DNS提供商"
|
:placeholder="t('certd.verifyPlan.dnsProvider')"
|
||||||
@change="onPlanChanged"
|
@change="onPlanChanged"
|
||||||
@selected-change="onDnsProviderChange(item, $event)"
|
@selected-change="onDnsProviderChange(item, $event)"
|
||||||
></fs-dict-select>
|
></fs-dict-select>
|
||||||
@@ -44,9 +44,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<a-divider type="vertical" />
|
<a-divider type="vertical" />
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<span class="label">DNS授权:</span>
|
<span class="label">{{ t("certd.verifyPlan.dnsAccess") }}:</span>
|
||||||
<span class="input">
|
<span class="input">
|
||||||
<access-selector v-model="item.dnsProviderAccessId" size="small" :type="item.dnsProviderAccessType || item.dnsProviderType" placeholder="请选择" @change="onPlanChanged"></access-selector>
|
<access-selector v-model="item.dnsProviderAccessId" size="small" :type="item.dnsProviderAccessType || item.dnsProviderType" :placeholder="t('certd.verifyPlan.pleaseSelect')" @change="onPlanChanged"></access-selector>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="item.type === 'http'" class="plan-http">
|
<div v-if="item.type === 'http'" class="plan-http">
|
||||||
<http-verify-plan v-model="item.httpVerifyPlan" @change="onPlanChanged" />
|
<http-verify-plan v-model="item.httpVerifyPlan" @change="onPlanChanged" />
|
||||||
<div class="helper">证书颁发机构将请求 https://yourdomain/.well-known/acme-challenge/xxxxxx 来验证域名所有权。</div>
|
<div class="helper">{{ t("certd.verifyPlan.httpHelper") }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -72,6 +72,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch } from "vue";
|
import { ref, watch } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import { dict, FsDictSelect } from "@fast-crud/fast-crud";
|
import { dict, FsDictSelect } from "@fast-crud/fast-crud";
|
||||||
import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
|
import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
|
||||||
import CnameVerifyPlan from "./cname-verify-plan.vue";
|
import CnameVerifyPlan from "./cname-verify-plan.vue";
|
||||||
@@ -84,17 +85,19 @@ defineOptions({
|
|||||||
name: "DomainsVerifyPlanEditor",
|
name: "DomainsVerifyPlanEditor",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const challengeTypeOptions = ref<any[]>([
|
const challengeTypeOptions = ref<any[]>([
|
||||||
{
|
{
|
||||||
label: "DNS验证",
|
label: t("certd.verifyPlan.dnsChallenge"),
|
||||||
value: "dns",
|
value: "dns",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "CNAME验证",
|
label: t("certd.verifyPlan.cnameChallenge"),
|
||||||
value: "cname",
|
value: "cname",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "HTTP验证",
|
label: t("certd.verifyPlan.httpChallenge"),
|
||||||
value: "http",
|
value: "http",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
+7
-6
@@ -1,5 +1,6 @@
|
|||||||
import Validator from "async-validator";
|
import Validator from "async-validator";
|
||||||
import { DomainsVerifyPlanInput } from "./type";
|
import { DomainsVerifyPlanInput } from "./type";
|
||||||
|
import { $t } from "/@/locales";
|
||||||
|
|
||||||
function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
|
function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@@ -13,7 +14,7 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
|
|||||||
for (const subDomain of subDomains) {
|
for (const subDomain of subDomains) {
|
||||||
const plan = value[domain].cnameVerifyPlan[subDomain];
|
const plan = value[domain].cnameVerifyPlan[subDomain];
|
||||||
if (plan.status !== "valid") {
|
if (plan.status !== "valid") {
|
||||||
throw new Error(`域名${subDomain}的CNAME未验证通过,请先设置CNAME记录,点击验证按钮`);
|
throw new Error($t("certd.verifyPlan.errors.cnameNotValid", { domain: subDomain }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +23,7 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
|
|||||||
for (const item of domains) {
|
for (const item of domains) {
|
||||||
//如果有通配符域名则不允许使用http校验
|
//如果有通配符域名则不允许使用http校验
|
||||||
if (item.startsWith("*.")) {
|
if (item.startsWith("*.")) {
|
||||||
throw new Error(`域名${item}为通配符域名,不支持HTTP校验`);
|
throw new Error($t("certd.verifyPlan.errors.wildcardNotSupportHttp", { domain: item }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,19 +32,19 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
|
|||||||
for (const subDomain of subDomains) {
|
for (const subDomain of subDomains) {
|
||||||
const plan = value[domain].httpVerifyPlan[subDomain];
|
const plan = value[domain].httpVerifyPlan[subDomain];
|
||||||
if (!plan.httpUploaderType) {
|
if (!plan.httpUploaderType) {
|
||||||
throw new Error(`域名${subDomain}的上传方式必须填写`);
|
throw new Error($t("certd.verifyPlan.errors.uploadMethodRequired", { domain: subDomain }));
|
||||||
}
|
}
|
||||||
if (!plan.httpUploaderAccess) {
|
if (!plan.httpUploaderAccess) {
|
||||||
throw new Error(`域名${subDomain}的上传授权信息必须填写`);
|
throw new Error($t("certd.verifyPlan.errors.uploadAccessRequired", { domain: subDomain }));
|
||||||
}
|
}
|
||||||
if (!plan.httpUploadRootDir) {
|
if (!plan.httpUploadRootDir) {
|
||||||
throw new Error(`域名${subDomain}的网站根路径必须填写`);
|
throw new Error($t("certd.verifyPlan.errors.websiteRootRequired", { domain: subDomain }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (type === "dns") {
|
} else if (type === "dns") {
|
||||||
if (!value[domain].dnsProviderType || !value[domain].dnsProviderAccessId) {
|
if (!value[domain].dnsProviderType || !value[domain].dnsProviderAccessId) {
|
||||||
throw new Error(`DNS模式下,域名${domain}的DNS类型和授权信息必须填写`);
|
throw new Error($t("certd.verifyPlan.errors.dnsProviderRequired", { domain }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="api-test">
|
<div class="api-test">
|
||||||
<div>
|
<div>
|
||||||
<fs-button :loading="loading" type="primary" text="测试" icon="ion:refresh-outline" @click="doTest"></fs-button>
|
<fs-button :loading="loading" type="primary" :text="t('certd.pluginCommon.test')" icon="ion:refresh-outline" @click="doTest"></fs-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="helper" :class="{ error: hasError }">
|
<div class="helper" :class="{ error: hasError }">
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
|
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
|
||||||
import { ref, inject } from "vue";
|
import { ref, inject } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import { Form } from "ant-design-vue";
|
import { Form } from "ant-design-vue";
|
||||||
import { getInputFromForm } from "./utils";
|
import { getInputFromForm } from "./utils";
|
||||||
|
|
||||||
@@ -19,6 +20,8 @@ defineOptions({
|
|||||||
name: "ApiTest",
|
name: "ApiTest",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const fromType: any = inject("getFromType");
|
const fromType: any = inject("getFromType");
|
||||||
const getScope: any = inject("get:scope");
|
const getScope: any = inject("get:scope");
|
||||||
const getPluginType: any = inject("get:plugin:type", () => {
|
const getPluginType: any = inject("get:plugin:type", () => {
|
||||||
@@ -61,14 +64,14 @@ const doTest = async () => {
|
|||||||
{
|
{
|
||||||
onError(err: any) {
|
onError(err: any) {
|
||||||
hasError.value = true;
|
hasError.value = true;
|
||||||
message.value = `错误:${err.message}`;
|
message.value = t("certd.pluginCommon.errorWithMessage", { message: err.message });
|
||||||
},
|
},
|
||||||
showErrorNotify: false,
|
showErrorNotify: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
message.value = "测试请求成功";
|
message.value = t("certd.pluginCommon.testRequestSuccess");
|
||||||
if (res) {
|
if (res) {
|
||||||
message.value += `,返回:${JSON.stringify(res)}`;
|
message.value += t("certd.pluginCommon.responseSuffix", { response: JSON.stringify(res) });
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { inject, ref, watch } from "vue";
|
import { inject, ref, watch } from "vue";
|
||||||
|
import { useI18n } from "/@/locales";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "CertDomainsGetter",
|
name: "CertDomainsGetter",
|
||||||
@@ -24,6 +25,7 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pipeline: any = inject("pipeline");
|
const pipeline: any = inject("pipeline");
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
function findStepFromPipeline(targetStepId: string) {
|
function findStepFromPipeline(targetStepId: string) {
|
||||||
for (const stage of pipeline.value.stages) {
|
for (const stage of pipeline.value.stages) {
|
||||||
@@ -40,7 +42,7 @@ function findStepFromPipeline(targetStepId: string) {
|
|||||||
const errorRef = ref("");
|
const errorRef = ref("");
|
||||||
function getStepIdFromInputKey(inputKey: string) {
|
function getStepIdFromInputKey(inputKey: string) {
|
||||||
if (!inputKey) {
|
if (!inputKey) {
|
||||||
errorRef.value = "请先选择域名证书";
|
errorRef.value = t("certd.pluginCommon.selectCertFirst");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return inputKey.split(".")[1];
|
return inputKey.split(".")[1];
|
||||||
@@ -49,7 +51,7 @@ function getDomainFromPipeline(inputKey: string) {
|
|||||||
let targetStepId = getStepIdFromInputKey(inputKey);
|
let targetStepId = getStepIdFromInputKey(inputKey);
|
||||||
let certStep = findStepFromPipeline(targetStepId);
|
let certStep = findStepFromPipeline(targetStepId);
|
||||||
if (!certStep) {
|
if (!certStep) {
|
||||||
errorRef.value = "找不到目标步骤,请先选择域名证书";
|
errorRef.value = t("certd.pluginCommon.targetStepNotFound");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +60,7 @@ function getDomainFromPipeline(inputKey: string) {
|
|||||||
targetStepId = getStepIdFromInputKey(firstLevelValue);
|
targetStepId = getStepIdFromInputKey(firstLevelValue);
|
||||||
certStep = findStepFromPipeline(targetStepId);
|
certStep = findStepFromPipeline(targetStepId);
|
||||||
if (!certStep) {
|
if (!certStep) {
|
||||||
errorRef.value = "找不到目标步骤,请先选择域名证书";
|
errorRef.value = t("certd.pluginCommon.targetStepNotFound");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,12 @@
|
|||||||
<template v-if="search">
|
<template v-if="search">
|
||||||
<div class="flex w-full items-center justify-between flex-wrap" style="padding: 4px 8px">
|
<div class="flex w-full items-center justify-between flex-wrap" style="padding: 4px 8px">
|
||||||
<div class="flex-1 flex flex-row items-center">
|
<div class="flex-1 flex flex-row items-center">
|
||||||
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear placeholder="这里可以搜索域名(数据来自“设置->域名管理”),您也可以直接在上面输入框输入" @keydown.enter="doSearch" />
|
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.pluginCommon.domainSearchPlaceholder')" @keydown.enter="doSearch" />
|
||||||
<fs-button type="primary" class="m-1" :loading="loading" icon="mingcute:search-2-line" @click="doSearch"> 查询 </fs-button>
|
<fs-button type="primary" class="m-1" :loading="loading" icon="mingcute:search-2-line" @click="doSearch">{{ t("certd.pluginCommon.search") }}</fs-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="manager flex flex-row items-center">
|
<div class="manager flex flex-row items-center">
|
||||||
<fs-button type="primary" class="m-1" icon="mingcute:vip-1-line" @click="openDomainImportDialog">导入域名</fs-button>
|
<fs-button type="primary" class="m-1" icon="mingcute:vip-1-line" @click="openDomainImportDialog">{{ t("certd.pluginCommon.importDomain") }}</fs-button>
|
||||||
<fs-button class="m-1" type="primary" icon="carbon:gui-management" @click="openDomainManager">管理域名</fs-button>
|
<fs-button class="m-1" type="primary" icon="carbon:gui-management" @click="openDomainManager">{{ t("certd.pluginCommon.manageDomain") }}</fs-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hasError" class="helper p-2" :class="{ error: hasError }">
|
<div v-if="hasError" class="helper p-2" :class="{ error: hasError }">
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-select>
|
</a-select>
|
||||||
<div class="ml-5">
|
<div class="ml-5">
|
||||||
<fs-button :loading="loading" title="刷新我的域名列表" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
|
<fs-button :loading="loading" :title="t('certd.pluginCommon.refreshMyDomains')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="helper" :class="{ error: hasError }">
|
<div class="helper" :class="{ error: hasError }">
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, defineComponent, onMounted, ref, Ref, useAttrs } from "vue";
|
import { computed, defineComponent, onMounted, ref, Ref, useAttrs } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { Dicts } from "../lib/dicts";
|
import { Dicts } from "../lib/dicts";
|
||||||
import { request } from "/@/api/service";
|
import { request } from "/@/api/service";
|
||||||
@@ -67,6 +68,8 @@ defineOptions({
|
|||||||
name: "DomainSelector",
|
name: "DomainSelector",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const VNodes = defineComponent({
|
const VNodes = defineComponent({
|
||||||
props: {
|
props: {
|
||||||
vnodes: {
|
vnodes: {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user